使用 shared_ptr 时出现分段错误

Segmentation fault when using a shared_ptr

我正在制作一个粒子系统,但我正在为如何构建我的代码而苦苦挣扎。这个想法是用户可以创建一个或多个 ParticleEmitter 对象,这些对象通过 ofxCurlNoise 对象传递给 ParticleManager 对象。

现在,我希望当用户更新 ParticleEmitters 对象时,ParticleManager 对象可以看到所做的更改。所以我使用了共享指针,但我在不同的时间有段错误,无论我使用一个ParticleEmitter(程序启动时的段错误)还是vector<ParticleEmitter>(程序退出时的段错误)。

这有什么问题?是否有一种设计模式可以完成我想做的事情?

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

这不是 std::shared_ptr 的工作方式。您正在堆栈上创建 ParticleEmitter 的实例,但 std::shared_ptr 用于管理在堆上创建的实例。在您的代码中,当您向 ParticleManager 添加一个新的发射器并将其包装到一个共享指针中时,发射器会在 particleEmitters 向量被销毁时被销毁(反过来,您的 ofApp实例被销毁)因此无论如何都会被销毁。

ofApp 的实例被销毁时,ofxCurlNoiseparticleEmitters 的实例都会被销毁(按此顺序)。因此 ofxCurlNoise 将依次破坏管理您的共享指针的 particleManager,然后它将删除您的粒子发射器(最初是在堆栈上创建的)。完成所有这些后,particleEmitters 矢量将被销毁,运行时系统将再次尝试销毁您的粒子发射器,从而导致您看到的错误。

此外,共享指针用于对共享所有权语义建模,我在您的用例中看不到这一点。我认为您最好使用 std::unique_ptr 来管理在堆上创建的实例,或者根本不使用智能指针并在堆栈上创建所有内容(您几乎已经在做)。

你永远不应该像你在这里做的那样从普通指针创建 shared_ptr:

 shared_ptr<ParticleEmitter>(&e)

这会尝试释放 ParticleEmitter 两次。一旦包含 ParticleEmitter 对象的向量超出范围,一旦 shared_ptr 超出范围。

void ofxCurlNoise::setup(vector<ParticleEmitter>& emitters, int n){
   for(auto& e : emitters){
    particleManager.addEmitter(shared_ptr<ParticleEmitter>(&e));
  }
  setup(n);
}

看起来你正在从 "stack" 分配共享指针 objects.You 应该使用 newmake_shared<ParticleEmitter> 构造 ParticleEmitter 对象,但实际情况是当向量被调整大小并且 ParticleEmitter 被复制到新位置时 shared_ptr<ParticleEmitter> 指向错误的地址。此外,当 vector 超出作用域时,元素会被破坏。

将指针传递给 shared_ptr 时,后者会取得它的所有权并对其进行管理。当你传递一个指向一个已经由 std::vector 管理的对象的指针时,它迟早会被删除两次,这当然是行不通的。 shared_ptr 必须传递一个尚未由另一个 class 管理的指针。

所以代替:

shared_ptr<ParticleEmitter>(&e)

您必须创建 ParticleEmitter 对象的副本

使用:

shared_ptr<ParticleEmitter>(new ParticleEmitter(e))

或更好:

std::make_shared<ParticleEmitter>(e)

这两种方法都需要ParticleEmitter有一个复制构造函数。

如果 ParticleEmitter 是一个沉重的 class 并且您想避免对其进行深层复制,那么它必须实现移动语义(移动构造函数)并使用:

std::make_shared<ParticleEmitter>(std::move(e))