2

我最近遇到了以下内存错误,在这里很容易发现,但在更复杂的代码中更难检测到:

class Foo : public IFoo {
  const Bar& bar_;
public:
  Foo(const Bar& bar) : bar_(bar) {
  }
  void test() {
    // access bar_ here
  }
};

int baz() {
  IFoo* foo = NULL;
  if(whatever) {
    Bar bar;
    foo = new Foo(bar);
  }
  else {
    // create other foo's
  }
  foo->test(); // segmentation fault
}

错误是Bar立即超出范围,被销毁然后在foo->test(). 一种解决方案是Bar在堆上创建,使用Bar* bar = new Bar(). 但是,我不喜欢这样做,因为我必须将Bar* bar指针保持在顶层,这样我才能delete在最后访问它,即使Bar是特定于该特定代码块的东西if(whatever){}

另一个解决方案是boost::shared_ptr<Bar>,但我不能只写这个:

  if(whatever) {
    boost::shared_ptr<Bar> bar(new Bar());
    foo = new Foo(*bar);
  }

因为shared_ptr也立即超出范围,破坏了包含的对象。

所以简而言之,为了摆脱这个问题,我必须在shared_ptr任何地方使用,inFoo作为成员变量,inFoo的构造函数等。为了消除这些问题,我所有的 API 等都必须使用shared_ptr,这很友好丑陋的。但是,这样做是正确的吗?到目前为止,我有时使用它来创建引用计数对象,但我的 API 保持干净shared_ptr。你如何处理这个问题,一旦你使用,shared_ptr你必须在任何地方使用它?

(另外,如果你使用这些引用计数的指针,你必须开始担心你是否真的想要shared_ptr或者更确切地说weak_ptr等等)

而且,我将使用什么来等效于Foo(const Bar& bar)Foo(const shared_ptr<const Bar> bar)?

当然,另一种选择是在自己的Bar对象和其他对象中添加引用计数,使用pimpl自己的计数器,但作为一般规则,这太乏味了。

4

6 回答 6

10

实际上我确实在任何地方都使用shared_ptr......有几种方法可以让它看起来不那么混乱。我使用的一个约定是每个定义的类的 typedefs:

class AClass {
public:
    typedef boost::shared_ptr<AClass> Ptr;
    typedef boost::weak_ptr<AClass> Ref;
    //...
};

使代码更具可读性:)

至于您的具体问题,请记住您可以通过指针传递 bar - 您必须记住在堆上分配它。

于 2010-02-03T13:58:20.830 回答
2

最好不要将 bar byref 传递给 Foo(换句话说,更改 Foos 构造函数)或复制它以便在 Foo 中保存一个副本。当然,在某些情况下这是不可能的。

关于用 覆盖您的代码shared_ptr<Bar>,最简单的解决方案是 typedef shared_ptr<Bar>toBarPtr或类似的。

于 2010-02-03T13:55:51.997 回答
1

我了解您提供了代码的简化版本,没有看到其余部分,我不知道我现在所说的是否可行。

既然foo是寿命最长的对象,那么如何在Bar其中创建实例,并使其他变量(bar在您的代码中)引用它?

另一方面, ifBar对于 if 块的内部来说确实是特定的,test()根本不需要它。Foo在这种情况下,将指针保留在内部,然后在 if 块的末尾删除它是没有问题的。否则,也许它不是特定于块的,您必须重新考虑您的设计。

于 2010-02-03T14:34:48.643 回答
1

我实际上会提出另一个解决方案......

我不太喜欢引用计数,不仅是杂乱无章,还因为当多个对象拥有同一个项目的句柄时,更难推理。

class IBar
{
public:
  virtual ~IBar() {}
  virtual IBar* clone() const;
};

class Foo: public IFoo
{
public:
  Foo(const IBar& ibar): m_bar(ibar.clone()) {}
  Foo(const Foo& rhs): m_bar(rhs.m_bar->clone()) {}
  Foo& operator=(const Foo& rhs) { Foo tmp(rhs); this->swap(tmp); return *this; }
  virtual ~Foo() { delete m_bar; }

  void swap(Foo& rhs) { std::swap(m_bar, rhs.m_bar); }

private:
  IBar* m_bar;
};

现在你的代码可以工作了,因为我做了一个副本:)

当然,如果 Bar 不是多态的,那就容易多了。

所以,即使它没有使用参考,你确定你真的需要一个吗?


现在让我们偏离正轨,因为您做错了很多事情:

  • foo = new Foo(bar)return不是很漂亮,如果发生异常或在if混乱中引入一些异常,您将如何保证删除内存?您应该转向为您管理内存的对象或C++0x 或 Boostauto_ptr的新对象。unique_ptr
  • 而不是使用引用计数,为什么不Foo获取所有权?Foo(std::auto_ptr<Bar> bar)也可以。

在不需要的地方避免引用计数。每次您分享某些内容时,实际上都会在代码中引入难以跟踪的错误(以及潜在的副作用)的来源。这就是Haskell功能性家庭越来越受欢迎的原因:)

于 2010-02-03T16:04:12.930 回答
1

此外,如果你使用这些引用计数的指针,你必须开始担心你是否真的想要 shared_ptr 或者更确切地说是 weak_ptr 等

如果您遵循您的设计并且您了解什么是weak_ptr(提示:它不是参考)和它的用途(提示:它不是“打破循环”),那就不用担心了。

如果你开始担心,那是因为你从来没有设计过,或者你不了解shared_ptr设计。

于 2011-10-20T03:27:56.663 回答
0

您可以简单地将 bar 对象移动到更高的范围,以防止过早删除它。

int baz() {
  Bar bar;
  IFoo* foo = NULL;
  if(whatever) {
    foo = new Foo(bar);
  }
  else {
    // create other foo's
  }
  foo->test(); // no more segmentation fault
}
于 2010-02-03T14:25:12.163 回答