在shared_ptr上调用方法时如何避免分段错误?

时间:2015-08-27 16:27:39

标签: c++ segmentation-fault shared-ptr

示例程序:

#include <memory>
#include <iostream>

class D;

class C {
public:
  C();
  void callD();
  void replaceD(D* d);
private:
  std::shared_ptr<D> d;
};

class D {
public:
  D(C* c);
  void call();
private:
  virtual void print();
  C* c;
};

C::C() : d(new D(this)) {}

void C::callD() {
  d->call();
}

void C::replaceD(D* d) {
  this->d = std::shared_ptr<D>(d);
}

D::D(C* c) : c(c) {}

void D::call() {
  c->replaceD(new D(c));
  print();
}

void D::print() {
  std::cout << "Hello, World!" << std::endl;
}

int main(void) {
  auto c = new C();
  c->callD();
  return 0;
}

(使用gcc:g++ -std=c++11 tmp.cpp -o tmp && ./tmp

会发生什么:

  • C::callD()来电d->call()
  • D::call()来电c->replaceD()
  • C::replaceD()重新分配C的{​​{1}}指针,导致旧的d被删除
  • d尝试调用虚拟方法D::call() - 但print()删除了当前的d实例!
  • 细分错误

解决方法:在C::replaceD()中插入auto d_ = d;,以便在C::callD()完成之前不会删除shared_ptr。但这看起来很像一个可以优化的未使用变量(尽管g ++似乎并没有在C::callD()上删除它)。

-O3返回之前,任何ptr->method()来电都不应该提高引用次数,以避免在method()的{​​{1}}过早删除时出现此问题?

2 个答案:

答案 0 :(得分:1)

您可以将std::shared_ptr引用传递给C::replaceD,在C被删除之前可以获得D当前void C::replaceD(D* d, std::shared_ptr<D>& tmp) { tmp.swap(this->d); this->d = std::shared_ptr<D>(d); } 的所有权。

D

然后D::call可以保持活着直到void D::call() { std::shared_ptr<D> tmp {}; c->replaceD(new D(c), tmp); print(); }

结束
{{1}}

我不得不说,你的代码看起来设计得很差,我首先会考虑是否有更好的设计可以完全避免这个问题。

答案 1 :(得分:1)

  

在method()返回之前,不应该在任何ptr-&gt; method()调用上增加引用计数,以避免在方法过早删除的情况下出现此问题?

没有。 std::shared_ptr的语义是严格的指针引用,而不是函数调用。该对象需要在另一个对象或堆栈中引用。你正在删除参考文献。

void C::replaceD(D* d) {
  this->d = std::shared_ptr<D>(d);
}

此代码也可能是delete this而不是c->replaceD(new D(c))

void D::call() {
  c->replaceD(new D(c));
  print();
}

我不确定你要做什么,但是如果你需要保持对D的引用,你可以重写C::replaceD

std::shared_ptr<D> C::replaceD(D* d) {

  std::shared_ptr<D> old = this->d;

  this->d = std::shared_ptr<D>(d);

  return old;
}

然后重写D::call

void D::call() {
  std::shared_ptr<D> prev = c->replaceD(new D(c));
  prev->print();
}