【问题标题】:Segmentation fault when using a shared_ptr使用 shared_ptr 时出现分段错误
【发布时间】:2015-02-23 09:48:11
【问题描述】:

我正在制作一个粒子系统,但我正在为如何构建我的代码而苦苦挣扎。这个想法是用户可以创建一个或多个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);
};

粒子管理器.cpp

#include "ParticleManager.h"

void ParticleManager::setup(int n){
    //...
}

void ParticleManager::addEmitter(const shared_ptr<ParticleEmitter>& emitter){
    emitters.push_back(emitter);
}

【问题讨论】:

  • 引用 SO 指南:“寻求调试帮助的问题必须包括所需的行为、特定问题或错误以及在问题本身中重现它所需的最短代码。没有明确问题陈述的问题是没有用的给其他读者。”
  • @UlrichEckhardt 这个确实实际上包含识别错误所需的代码和相当清晰的问题陈述。这不是一个很好的问题,但我见过更糟糕的情况。
  • std::shared_ptr 中,您只能保留动态创建的对象(由new)...据我所知-您将传递给std::shared_ptr vector&lt;ParticleEmitter&gt; 中的任何内容...
  • 它没有main(),它有一堆不同的类和函数,所以既有缺失又有多余的部分。这与引用的规则 @Angew 形成对比。
  • 是的,如果我不够清楚,对不起。这是openFrameworks的代码,程序启动时会调用ofApp::setup()

标签: c++ c++11 design-patterns overloading shared-ptr


【解决方案1】:

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

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

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

【讨论】:

  • +1,有一点点挑剔:OP 将 shared_ptrs 指向 vector 中的元素,而不是局部变量。
  • 好的,我现在明白了。所以 shared_ptr 不是正确的工具。但问题是我不想在ofApp 类中使用指向ParticleEmitters 对象的指针。但是,必须在 ParticleManager 中看到对 ofApp 中的 ParticleEmitter 所做的更改。我怎样才能做到这一点?
  • @ElieGnrd 首先,您需要澄清所有权语义。谁拥有粒子发射器,即谁应该负责管理它们的生命周期?
  • ofApp 拥有ParticleEmitters
  • 您可以在堆上创建发射器并将它们存储在 ofApp 中的 std::unique_ptrs 向量中。然后,您可以将实例作为原始指针传递,也可以传递对向量的引用。或者您可以在堆栈上创建它们并让它们由向量管理,并传递对向量的引用。
【解决方案2】:

你不应该像这里那样从普通指针创建 shared_ptr:

 shared_ptr<ParticleEmitter>(&e)

这会尝试释放 ParticleEmitter 两次。一次作为持有 ParticleEmitter 对象的向量超出范围,一次作为 shared_ptr 超出范围。

【讨论】:

  • 内置指针,例如ParticleEmitter*,不是智能指针。
  • 那你在创建shared_ptr的时候需要一个普通的指针,没有别的办法使用
  • 最佳实践是使用 make_shared() 创建 shared_ptr。但你是对的,我本来可以更准确的。
【解决方案3】:
void ofxCurlNoise::setup(vector<ParticleEmitter>& emitters, int n){
   for(auto& e : emitters){
    particleManager.addEmitter(shared_ptr<ParticleEmitter>(&e));
  }
  setup(n);
}

看起来您正在从“堆栈”分配的对象中创建共享指针。您应该使用newmake_shared&lt;ParticleEmitter&gt; 构造ParticleEmitter 对象,但是当调整向量的大小并将ParticleEmitter 复制到新对象时会发生这种情况位置shared_ptr&lt;ParticleEmitter&gt; 指向错误的地址。另外,当向量超出作用域时,元素会被破坏。

【讨论】:

  • e 不是堆栈对象,它是对 std::vector 管理的对象的引用
  • 它不在堆栈上,但看起来确实如此。除了由向量控制的销毁之外,当向量的大小发生变化时,它还会移动到另一个内存位置。
【解决方案4】:

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

所以而不是:

shared_ptr<ParticleEmitter>(&e)

您必须创建ParticleEmitter 对象的副本

用途:

shared_ptr<ParticleEmitter>(new ParticleEmitter(e))

或者更好:

std::make_shared<ParticleEmitter>(e)

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

如果ParticleEmitter 是一个重类并且你想避免对其进行深拷贝,那么它必须实现移动语义(移动构造函数)并使用:

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

【讨论】:

    猜你喜欢
    • 2020-06-15
    • 1970-01-01
    • 2017-07-20
    • 2021-11-12
    • 1970-01-01
    • 2017-06-06
    • 2017-08-26
    • 2018-06-18
    • 2019-05-10
    相关资源
    最近更新 更多