ofNode parenting not working when ofNode is in std::vector

hi, I’m having some problems using ofNode when ofNode is part of a class inside an std::vector
here is a minimal reproducible example where parented geometry isnt moving along with geometry.

here is a link : https://github.com/itsKaspar/ofbutterflyproblem

this may also be my bad coding practice, if so, I’d love some advice because a lot of my particle systems are based on std::vector right now

Hi, the problem lies in your code. It is not a bug.
when you use std::vector's push_back a copy of what you are passing is made and stored in the vector. As ofNode relies on keeping a pointer to its parent when the vector creates this copy it messes up with the pointer, as the copy will have a pointer to the eleements that were constructed for the original.
There are several workarounds.
1 You can use a setup function in your void so the ofNode’s parenting is performed post copy on the actual object stored in the vector.

void ofApp::setup(){
	boids.push_back(Boid());
	boids.back().setup(glm::vec3(ofGetWidth()/2, ofGetWidth() / 2,0));
}

class Boid
{

public:
	Boid(){};
	Boid(glm::vec3 p)
	{
		setup(p);
	}
	void setup(glm::vec3 p)
	{
		
		vel = glm::vec3(0.1, 0.1, 0.1);
		
		butterfly.move(p);
		
		lWing.setParent(butterfly);
		rWing.setParent(butterfly);
		lWing.move(-110, 0,0);
		rWing.move(110, 0, 0);
		
	}
...
};

This is the simplest solution but you can run into the problem that std::vector realocates itself when you expand it and that will mess up the pointers.
Although, if you are only pushing elements to the vector once during setup you can do the following

void ofApp::setup(){
        boids.resize(100);// which ever amount of boids you might want to have
	for(auto& b: boids)// this is similar to the tipical for loop, it is a modern c++ thing. you type less. 
{
	b.setup(glm::vec3(ofGetWidth()/2, ofGetWidth() / 2,0));
}

}

if this works for you great, if not you will need to use either std::vector<std::shared_ptr<Boid> or std::vector<std::unique_ptr<Boid> to store the elements, and it will work regardless of the vector expanding and reallocating itself.

shared_ptr and unique_ptr are the so called “smart pointers”, which will be in charge of deleting their allocated memory once destroyed, saving you from having to take care of such (which tended to be one of the biggest sources of memory leaks and bugs). This are a feature of modern C++ and its use is highly recommended and endorsed instead of using raw pointers. Their major difference is that that the shader_ptr allows you to share that pointer and have several instances of a shared_ptr pointing to the same data, which is all managed by it internally. The unique_ptr does not allow you to have such sort of shared instances and you can not copy it, but it is the simplest one and introduces almost no overhead, while the shared_ptr does introduce a very slight overhead (probably unnoticed most of times).

if you use either of these smart pointers you dont need to change the Boid class

it should go like

// in ofApp.h
vector<unique_ptr<Boid>> boids;
//in setup
	boids.emplace_back(make_unique<Boid>(glm::vec3(ofGetWidth()/2, ofGetWidth() / 2,0)));
// in ofApp.h
vector<shared_ptr<Boid>> boids;
//in setup
	boids.push_back(make_shared<Boid>(glm::vec3(ofGetWidth()/2, ofGetWidth() / 2,0)));
1 Like

Thanks so much for clearing that out ! I’ll read up on shared pointer asap too :slight_smile:

no prob.
btw,
When you use either smart pointer you need to change

boids[i].update();// dot operator access

to

boids[i]->update();

as these will behave as pointers

and if you dont know or understand well what pointers are start reading about regular pointers, before jumping into smart pointers.
https://openframeworks.cc/ofBook/chapters/memory.html

1 Like

Hi @roymacdonald, thanks for your explanation of smart pointers above! I was just recently thinking about putting some of3dPrimitivies into a vector. So, after reading this thread, I looked at the ofNode class and found it has an ofNode* parent.

So, do you think using std::move would work too, to make an object and then move it into the vector? Also, if the pointer parent were a smart pointer inside of ofNode, maybe a shared pointer, would an ofNode object still have the above issue when its copied into a vector with .push_back()? I’m thinking it wouldn’t, but I’m still newish with smart pointers too.

Hi, I think that using std::move might work but then the problem is that the vector can “decide” to reallocate itself and when that happens the pointers will be messed up. The std::vector guarantees you all of its elements memory will be contiguous, so when you push something and there is not enough memory for guaranteeing such it will move to a new memory region.
Having a the parent as a shared_ptr is not a solution either as you need then that all the ofNode instances to be a shared_ptr rather than regular instances, not just on your specific app but in the whole OF environment. Having a raw pointer is safe and the most straight forwards thing but you must be aware of it when you use std::vectors. And the easiest work around for such is to store these in the vector as a smart pointers. Implementation changes very little and it is quite easy to do.