Лучший способ управлять владением памятью в C ++? Shared-указатели или другие механизмы? - PullRequest
2 голосов
/ 06 декабря 2010

В новом фрагменте кода у меня есть несколько разных классов, которые ссылаются друг на друга.Примерно так (это не моя настоящая ситуация, а пример чего-то похожего):

class BookManager
   {
   ...
   };

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

Каждая книга относится к менеджеру книг, но проблема в том, что многие книги будут иметь свой собственный BookManager, нонекоторые книги могут иметь общий BookManager.

На самом деле вызывающая сторона не указывает, что Книга должна делать со своим BookManager, но в 90% случаев BookManager может быть уничтожен вместе с Книгой.Примерно в 10% случаев один и тот же BookManager используется повторно для нескольких книг, и BookManager нельзя удалять вместе с Книгой.

Удаление BookManager вместе с Книгой удобно в этих 90% случаев.так как вызывающая сторона 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>), но кажетсяменее «чистый», чем подход с общим указателем.

Другой альтернативой может быть использование typedef в BookManager, например:

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

, что позволяет нам написать это:

BookManager::Ptr bookManager;

Что больше похоже на обычный синтаксис указателя, чем на оригинальный синтаксис общего указателя.

Кто-нибудь имеет опыт работы с любым из этих подходов?Любые другие предложения?

Ответы [ 5 ]

7 голосов
/ 06 декабря 2010

В C ++, если у вас общий несогласованный доступ к общим объектам, тогда наиболее распространенный подход - это какой-то подсчет ссылок, который вы получаете из shared_ptr.

Единственный недостаток - это то, чтоне распространены в C ++ (особенно в библиотеках), поэтому вам иногда требуется доступ к необработанному указателю.В этих случаях вы должны быть осторожны, чтобы сохранить объект живым.

Я предлагаю, чтобы, если вы использовали shared_ptr - старались использовать его везде, если это невозможно.И да, используйте typedef, если хотите.

4 голосов
/ 06 декабря 2010

Вы, кажется, хорошо это продумали.Выглядит как идеальное использование для shared_ptr для меня.

В интересах добавления НЕКОТОРЫХ значений для вас, убедитесь, что вы смотрите на шаблоны для эффективного shared_ptr создания здесь .

2 голосов
/ 06 декабря 2010

В C ++ существует 2 вида "владения":

  • детерминированное владение: в любой момент вы можете указать, кто отвечает за ресурс
  • совместное владение: в любой момент может быть несколько владельцев, хотя их трудно определить

shared_ptr, как следует из названия, для второго случая. Это редко требуется, особенно с введением семантики перемещения и, следовательно, unique_ptr, но в тех случаях, когда это действительно необходимо, это неоценимо.

Глядя на ваше дело, у меня возникает вопрос: вам действительно нужно совместное владение?

Одним из типичных решений, позволяющих избежать совместного владения, является использование Factory, который будет единственным владельцем всех создаваемых им объектов и гарантирует, что они живы, пока сам Factory жив.

Это может быть "менее безопасно", чем использование shared_ptr, но есть очень интересные аргументы:

  • время жизни объектов еще раз детерминировано, гораздо проще отлаживать
  • нет риска случайного создания цикла ссылок (и утечки памяти)

Если только вы не ограничены в памяти (в этом случае чем раньше объекты будут удалены, тем лучше), вы можете использовать BookManagerFactory подход.

1 голос
/ 06 декабря 2010

Лу Франко уже дал вам ответ, но в качестве примечания: ваша вторая идея реализации, по сути, заключается в том, что делает auto_ptr (когда завладевает правдой).

Лучшим решением может быть наличие Книгикласс содержит дескриптор (например, индекс массива или хэш) для BookManager, который находится в некотором классе BookManagerCache.Класс Cache отвечает исключительно за управление временем жизни BookManager.

1 голос
/ 06 декабря 2010

Только когда вы точно знаете, где находится объект и позаботитесь об его удалении (!), Вы должны использовать голые указатели.У меня есть «стек» классов, каждый из которых содержит уникальный родительский указатель, и в этом случае счетчик ссылок всегда будет равен 1, и вы сможете получить доступ к самому глубокому элементу через последнего дочернего элемента.Похоже, у вас здесь гораздо более сложная настройка, используйте умные указатели.Помните, что даже если пустой указатель может показаться чище или проще, следует рекомендовать unique_ptr, но вам, возможно, придется бороться с назначением копии в коде предварительного преобразования и загадочными сообщениями об ошибках, возникающими в результате переключения.

...