I am making a particle system and I'm struggling with how to structure my code. The idea is that a user can create one or several ParticleEmitter
objects that are passed to a ParticleManager
object via the ofxCurlNoise
object.
Now, I want that when the user updates the ParticleEmitters
objects, the ParticleManager
object sees the changes made. So I used shared pointers but I have segmentation faults at different times, whether I use one ParticleEmitter
(segmentation fault when the program starts) or a vector<ParticleEmitter>
(segmentation fault when the program exits).
What is wrong with this? Is there a design pattern for doing what I'm trying to do?
ofApp.h
#include "ofxCurlNoise.h"
class ofApp : public ofBaseApp{
// ParticleEmitter particleEmitter;
vector<ParticleEmitter> particleEmitters;
ofxCurlNoise curlNoise;
public:
void setup();
};
ofApp.cpp
#include "ofApp.h"
void ofApp::setup(){
// This produces a segfault as soon as the program starts
// particleEmitter.setup();
// curlNoise.setup(particleEmitter, 1024*256);
// This produces a segfault when the program exits
ParticleEmitter emitter;
emitter.setup();
particleEmitters.push_back(emitter);
curlNoise.setup(particleEmitters, 1024*256);
}
ofxCurlNoise.h
#include "ParticleManager.h"
class ofxCurlNoise {
ParticleManager particleManager;
public:
void setup(ParticleEmitter& emitter, int n);
void setup(vector<ParticleEmitter>& emitters, int n);
private:
void setup(int n);
};
ofxCurlNoise.cpp
#include "ofxCurlNoise.h"
void ofxCurlNoise::setup(ParticleEmitter& emitter, int n){
particleManager.addEmitter(shared_ptr<ParticleEmitter>(&emitter));
setup(n);
}
void ofxCurlNoise::setup(vector<ParticleEmitter>& emitters, int n){
for(auto& e : emitters){
particleManager.addEmitter(shared_ptr<ParticleEmitter>(&e));
}
setup(n);
}
void ofxCurlNoise::setup(int n){
particleManager.setup(n);
}
ParticleManager.h
#include "ParticleEmitter.h"
class ParticleManager{
vector<shared_ptr<ParticleEmitter>> emitters;
public:
void addEmitter(const shared_ptr<ParticleEmitter>& emitter);
void setup(int n);
};
ParticleManager.cpp
#include "ParticleManager.h"
void ParticleManager::setup(int n){
//...
}
void ParticleManager::addEmitter(const shared_ptr<ParticleEmitter>& emitter){
emitters.push_back(emitter);
}
This is not how std::shared_ptr
works. You are creating your instances of ParticleEmitter
on the stack, but std::shared_ptr
is used to manage instances which are created on the heap. In your code, when you add a new emitter to the ParticleManager
, and wrap it into a shared pointer, the emitter is destroyed when the particleEmitters
vector is destroyed (when, in turn, your ofApp
instance is destroyed) and is thus destroyed regardless.
When the instance of ofApp
is destroyed, both the instance of ofxCurlNoise
and particleEmitters
are destroyed (in that order). So ofxCurlNoise will in turn destroy the particleManager
, which manages your shared pointers, which will then delete your particle emitters (which were originally created on the stack). After all that is done, the particleEmitters
vector is getting destroyed, and the runtime system will try to destroy your particle emitters again, leading to the error you are seeing.
Furthermore, shared pointers are used to model shared ownership semantics, which I don't see in your use case. I think you'd be better off to either use std::unique_ptr
to manage instances created on the heap, or to not use smart pointers at all and create everything on the stack (which you are almost doing already).
You should never create shared_ptr from an ordinary pointer as you do here:
shared_ptr<ParticleEmitter>(&e)
This tries to free the ParticleEmitter twice. Once as the vector holding the ParticleEmitter objects goes out of scope, and once as the shared_ptr goes out of scope.
void ofxCurlNoise::setup(vector<ParticleEmitter>& emitters, int n){
for(auto& e : emitters){
particleManager.addEmitter(shared_ptr<ParticleEmitter>(&e));
}
setup(n);
}
Looks like you are making shared pointer from "stack" allocated objects.You should construct ParticleEmitter
objects using new
or make_shared<ParticleEmitter>
, but what happens is that when vector is resized and ParticleEmitter
is copied on new location shared_ptr<ParticleEmitter>
points to wrong address. Additionaly when vector gets out of scoped elemnts get destructed .
When passing a pointer to a shared_ptr
, the latter takes ownership of it and manages it. As you pass a pointer to an object already managed by a std::vector, it will be deleted twice sooner or later, which cannot work of course. shared_ptr
must be passed a pointer which is not already managed by another class.
So instead of:
shared_ptr<ParticleEmitter>(&e)
You must create a copy of the ParticleEmitter
object
Use:
shared_ptr<ParticleEmitter>(new ParticleEmitter(e))
Or better:
std::make_shared<ParticleEmitter>(e)
Both methods require ParticleEmitter
to have a copy constructor.
If the ParticleEmitter
is a heavy class and you want to avoid making a deep-copy of it, then it must implement move semantics (move constructor) and use:
std::make_shared<ParticleEmitter>(std::move(e))