在C ++中管理内存所有权的最佳方法是什么?共享指针或其他机制?

时间:2010-12-06 16:33:15

标签: c++ c++11 shared-ptr

在一段新代码中,我有几个不同的类相互引用。这样的事情(这不是我的实际情况,而是一个类似的例子):

class BookManager
   {
   ...
   };

class Book
   {
   public:
      void setBookManager(BookManager *bookManager) {m_bookManager = bookManager;}
   private:
      BookManager *m_bookManager;
   };

每本书都指的是一本书经理,但问题是很多书都有自己特定的BookManager,但有些书可能会共享一个共同的BookManager。

调用者并没有真正指定Book应该对其BookManager做什么,但在大约90%的情况下,BookManager可以与Book一起销毁。在大约10%的情况下,相同的BookManager可以重复用于多本书籍,并且不得使用书籍删除BookManager。

在90%的情况下,将BookManager与Book一起删除非常方便,因为Book :: setBookManager的调用者不再需要记住BookManager。它本身就是书本身。

我看到了解决这个问题的两种替代解决方案。

首先是广泛使用共享指针。如果调用者之后在BookManager中不再感兴趣,则它不会保留共享指针。如果它仍然对它感兴趣,或者它希望BookManager在多本书上共享,它会保留共享指针并将其传递给那些多本书。

第二种选择是明确地告诉本书如何处理书的所有权,如下所示:

class Book
   {
   public:
      void setBookManager(BookManager *bookManager, book takeOwnership=true)
         {
         m_bookManager = bookManager;
         m_hasOwnership = takeOwnership;
         }
      ~Book()
         {
         if (m_hasOwnership && m_bookManager) delete m_bookManager;
         }
   private:
      BookManager *m_bookManager;
      bool m_hasOwnership;
   };

第二个解决方案似乎更容易,并且允许我们使用普通指针语法(BookManager *而不是std::shared_ptr<BookManager>),但它似乎比共享指针方法更“干净”。

另一种选择可能是在BookManager中使用typedef,如下所示:

class BookManager
   {
   public:
      typedef std::shared_ptr<BookManager> Ptr;
      ...
   };

这允许我们写这个:

BookManager::Ptr bookManager;

这看起来更像是普通的指针语法,而不是原始的共享指针语法。

有没有人有这两种方法的经验? 还有其他建议吗?

5 个答案:

答案 0 :(得分:7)

在C ++中,如果您对共同对象进行共享,非协调访问,那么最常见的方法是某种引用计数,您可以从shared_ptr获得。

唯一的缺点是它在C ++(尤其是库)中并不普遍,所以有时你需要访问原始指针。在这些情况下,您需要小心保持对象的活着。

我建议如果您使用shared_ptr - 尝试在任何地方使用它,除非它不可能。是的,如果你愿意,可以使用typedef。

答案 1 :(得分:4)

你似乎已经深思熟虑了。看起来像shared_ptr给我的理想用途。

为了为您添加SOME值,请务必查看模板,以便有效shared_ptr创建here

答案 2 :(得分:2)

C ++中有两种“所有权”:

  • 确定性所有权:您可以随时指出谁负责资源
  • 共享所有权:在任何时候都可能有几个所有者,但很难确定
顾名思义,

shared_ptr适用于第二种情况。很少需要它,特别是引入移动语义,因此unique_ptr,但在那些实际需要的情况下,它是非常宝贵的。

看看你的情况,我的问题是:你真的需要共享所有权吗?

避免共享所有权的一个典型解决方案是使用Factory,它将是它创建的所有对象的唯一所有者,并且只要Factory本身存在,就可以保证它们存活。

它可能比使用shared_ptr“更安全”,但有非常有趣的论点:

  • 对象的生命周期再次具有确定性,更容易调试
  • 没有意外创建引用循环(和泄漏内存)的风险

除非你受内存限制(在这种情况下,对象越早删除就越好),你可能希望在这里采用BookManagerFactory方法。

答案 3 :(得分:1)

只有当您确切知道对象所在的位置并注意将其删除(!)时,才应使用裸指针。我有一个类的“堆栈”,每个类都包含唯一的父指针,在这种情况下,引用计数始终为1,您可以通过最后一个子进入最深的元素。看起来你在这里有一个更复杂的设置,但是,请使用智能指针。请记住,即使裸指针看起来更干净或更容易,也应该推荐使用unique_ptr,但您可能需要在转换前代码中进行移动与复制分配以及切换产生的神秘错误消息。

答案 4 :(得分:1)

Lou Franco已经给了你答案,但是作为旁注:你的第二个实现想法基本上是auto_ptr所做的(当取得所有权时)。

更好的解决方案可能是让Book类将句柄(例如数组索引或散列)保存到驻留在某些BookManagerCache类中的BookManager。 Cache类完全负责管理BookManagers的生命周期。