Segmentation fault when using a shared_ptr

2019-06-03 04:44发布

问题:

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);
}

回答1:

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).



回答2:

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.



回答3:

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 .



回答4:

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))