Вопрос Об Умных указателях и их Неизбежном индетерминизме

StackOverflow https://stackoverflow.com/questions/400993

Вопрос

Последние два года я активно использую интеллектуальные указатели (boost:: shared_ptr, если быть точным) в своих проектах.Я понимаю и ценю их преимущества, и они мне в целом очень нравятся.Но чем больше я их использую, тем больше я скучаю по детерминированному поведению C ++ в отношении управления памятью и RAII, которые мне, кажется, нравятся в языке программирования.Интеллектуальные указатели упрощают процесс управления памятью и обеспечивают автоматическую сборку мусора среди прочего, но проблема в том, что использование автоматической сборки мусора в целом и интеллектуального указателя в частности вносит некоторую степень неопределенности в порядок инициализации (де).Этот индетерминизм отбирает контроль у программистов и, как я начал понимать в последнее время, делает работу по проектированию и разработке API, использование которых заранее не полностью известно на момент разработки, раздражающе трудоемкой, потому что все шаблоны использования и угловые варианты должны быть хорошо продуманы.

Чтобы уточнить подробнее, в настоящее время я разрабатываю API.Части этого API требуют, чтобы определенные объекты инициализировались до или уничтожались после других объектов.Другими словами, порядок (де) инициализации иногда важен.Чтобы привести вам простой пример, предположим, у нас есть класс под названием System.Система предоставляет некоторые базовые функциональные возможности (ведение журнала в нашем примере) и поддерживает ряд подсистем с помощью интеллектуальных указателей.

class System {
public:
    boost::shared_ptr< Subsystem > GetSubsystem( unsigned int index ) {
        assert( index < mSubsystems.size() );
        return mSubsystems[ index ];
    }

    void LogMessage( const std::string& message ) {
        std::cout << message << std::endl;
    }

private:
    typedef std::vector< boost::shared_ptr< Subsystem > > SubsystemList;
    SubsystemList mSubsystems;    
};

class Subsystem {
public:
    Subsystem( System* pParentSystem )
         : mpParentSystem( pParentSystem ) {
    }

    ~Subsystem() {
         pParentSubsystem->LogMessage( "Destroying..." );
         // Destroy this subsystem: deallocate memory, release resource, etc.             
    }

    /*
     Other stuff here
    */

private:
    System * pParentSystem; // raw pointer to avoid cycles - can also use weak_ptrs
};

Как вы уже можете сказать, Подсистема имеет смысл только в контексте Системы.Но подсистема в такой конструкции может легко пережить свою родительскую Систему.

int main() {
    {
        boost::shared_ptr< Subsystem > pSomeSubsystem;
        {
            boost::shared_ptr< System > pSystem( new System );
            pSomeSubsystem = pSystem->GetSubsystem( /* some index */ );

        } // Our System would go out of scope and be destroyed here, but the Subsystem that pSomeSubsystem points to will not be destroyed.

     } // pSomeSubsystem would go out of scope here but wait a second, how are we going to log messages in Subsystem's destructor?! Its parent System is destroyed after all. BOOM!

    return 0;
}

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

Хотя задача разработчика API не в том, чтобы защищать программистов-клиентов от самих себя, хорошей идеей будет сделать API простым в правильном использовании и трудным в неправильном.Поэтому я спрашиваю вас, ребята.А ты как думаешь?Как я должен решить эту проблему?Как бы вы спроектировали такую систему?

Заранее спасибо, Джош

Это было полезно?

Решение

Краткое описание проблемы

В этом вопросе есть две конкурирующие проблемы.

  1. Управление жизненным циклом Subsystems, позволяя удалять их в нужное время.
  2. Клиенты Subsystemвам нужно знать, что Subsystem то, что они используют, является действительным.

Обработка №1

System владеет Subsystems и должны управлять своим жизненным циклом в соответствии со своей собственной областью действия.Используя shared_ptrэто особенно полезно, поскольку упрощает уничтожение, но вы не должны раздавать их, потому что тогда вы теряете детерминизм, который вы ищете в отношении их освобождения.

Обработка №2

Это более интересная проблема, требующая решения.Описывая проблему более подробно, вам нужно, чтобы клиенты получали объект, который ведет себя как Subsystem в то время как это Subsystem (и это родительский System) существует, но ведет себя соответствующим образом после Subsystem уничтожен.

Это легко решается с помощью комбинации Шаблон прокси - сервера, тот самый Паттерн состояния и тот Шаблон нулевого объекта.Хотя это может показаться немного сложным решением, 'Есть простота, которую можно найти только по ту сторону сложности"Как разработчики библиотек и API, мы должны приложить все усилия, чтобы сделать наши системы надежными.Кроме того, мы хотим, чтобы наши системы интуитивно вели себя так, как ожидает пользователь, и изящно отказывали, когда они пытаются злоупотреблять ими.Существует множество решений этой проблемы, однако это должно подвести вас к тому важному моменту, когда, поскольку вы и Скотт Мейерс скажи, это так".прост в правильном использовании и труден в неправильном.'

Теперь я предполагаю, что на самом деле, System сделки в некотором базовом классе Subsystems, из которых вы выводите различные Subsystems.Я представил это ниже следующим образом SubsystemBase.Вам нужно представить Прокси - сервер объект, SubsystemProxy ниже, который реализует интерфейс SubsystemBase путем пересылки запросов к объекту, который он проксирует.(В этом смысле это очень похоже на специальное применение Шаблон Декоратора.) Каждый Subsystem создает один из этих объектов, который он хранит с помощью shared_ptr, и возвращает по запросу через GetProxy(), который вызывается родительским System возражать, когда GetSubsystem() называется.

Когда a System выходит за рамки, каждый из них Subsystem объекты уничтожаются.В своем деструкторе они вызывают mProxy->Nullify(), что вызывает их Прокси - сервер объекты, чтобы изменить их Состояние.Они делают это, меняя положение, чтобы указать на Нулевой объект, который реализует SubsystemBase интерфейс, но делает это, ничего не делая.

Используя Паттерн состояния это позволило клиентскому приложению полностью не обращать внимания на то, является ли конкретный Subsystem существует.Более того, ему не нужно проверять указатели или сохранять экземпляры, которые должны были быть уничтожены.

Тот Самый Шаблон прокси - сервера позволяет клиенту зависеть от легковесного объекта, который полностью раскрывает детали внутренней работы API и поддерживает постоянный, единообразный интерфейс.

Тот Самый Шаблон нулевого объекта позволяет Прокси - сервер функционировать после оригинала Subsystem был удален.

Пример кода

Я поместил здесь грубый пример качества псевдокода, но он меня не удовлетворил.Я переписал его, чтобы это был точный компилирующий (я использовал g ++) пример того, что я описал выше.Чтобы заставить это работать, мне пришлось ввести несколько других классов, но их использование должно быть ясно из их названий.Я нанял Одноэлементный Паттерн для NullSubsystem класс, поскольку логично, что вам не понадобится больше одного. ProxyableSubsystemBase полностью абстрагирует поведение Прокси-сервера от Subsystem, позволяя ему оставаться в неведении относительно такого поведения.Вот UML-диаграмма классов:

UML Diagram of Subsystem and System Hierarchy

Пример кода:

#include <iostream>
#include <string>
#include <vector>

#include <boost/shared_ptr.hpp>


// Forward Declarations to allow friending
class System;
class ProxyableSubsystemBase;

// Base defining the interface for Subsystems
class SubsystemBase
{
  public:
    // pure virtual functions
    virtual void DoSomething(void) = 0;
    virtual int GetSize(void) = 0;

    virtual ~SubsystemBase() {} // virtual destructor for base class
};


// Null Object Pattern: an object which implements the interface to do nothing.
class NullSubsystem : public SubsystemBase
{
  public:
    // implements pure virtual functions from SubsystemBase to do nothing.
    void DoSomething(void) { }
    int GetSize(void) { return -1; }

    // Singleton Pattern: We only ever need one NullSubsystem, so we'll enforce that
    static NullSubsystem *instance()
    {
      static NullSubsystem singletonInstance;
      return &singletonInstance;
    }

  private:
    NullSubsystem() {}  // private constructor to inforce Singleton Pattern
};


// Proxy Pattern: An object that takes the place of another to provide better
//   control over the uses of that object
class SubsystemProxy : public SubsystemBase
{
  friend class ProxyableSubsystemBase;

  public:
    SubsystemProxy(SubsystemBase *ProxiedSubsystem)
      : mProxied(ProxiedSubsystem)
      {
      }

    // implements pure virtual functions from SubsystemBase to forward to mProxied
    void DoSomething(void) { mProxied->DoSomething(); }
    int  GetSize(void) { return mProxied->GetSize(); }

  protected:
    // State Pattern: the initial state of the SubsystemProxy is to point to a
    //  valid SubsytemBase, which is passed into the constructor.  Calling Nullify()
    //  causes a change in the internal state to point to a NullSubsystem, which allows
    //  the proxy to still perform correctly, despite the Subsystem going out of scope.
    void Nullify()
    {
        mProxied=NullSubsystem::instance();
    }

  private:
      SubsystemBase *mProxied;
};


// A Base for real Subsystems to add the Proxying behavior
class ProxyableSubsystemBase : public SubsystemBase
{
  friend class System;  // Allow system to call our GetProxy() method.

  public:
    ProxyableSubsystemBase()
      : mProxy(new SubsystemProxy(this)) // create our proxy object
    {
    }
    ~ProxyableSubsystemBase()
    {
      mProxy->Nullify(); // inform our proxy object we are going away
    }

  protected:
    boost::shared_ptr<SubsystemProxy> GetProxy() { return mProxy; }

  private:
    boost::shared_ptr<SubsystemProxy> mProxy;
};


// the managing system
class System
{
  public:
    typedef boost::shared_ptr< SubsystemProxy > SubsystemHandle;
    typedef boost::shared_ptr< ProxyableSubsystemBase > SubsystemPtr;

    SubsystemHandle GetSubsystem( unsigned int index )
    {
        assert( index < mSubsystems.size() );
        return mSubsystems[ index ]->GetProxy();
    }

    void LogMessage( const std::string& message )
    {
        std::cout << "  <System>: " << message << std::endl;
    }

    int AddSubsystem( ProxyableSubsystemBase *pSubsystem )
    {
      LogMessage("Adding Subsystem:");
      mSubsystems.push_back(SubsystemPtr(pSubsystem));
      return mSubsystems.size()-1;
    }

    System()
    {
      LogMessage("System is constructing.");
    }

    ~System()
    {
      LogMessage("System is going out of scope.");
    }

  private:
    // have to hold base pointers
    typedef std::vector< boost::shared_ptr<ProxyableSubsystemBase> > SubsystemList;
    SubsystemList mSubsystems;
};

// the actual Subsystem
class Subsystem : public ProxyableSubsystemBase
{
  public:
    Subsystem( System* pParentSystem, const std::string ID )
      : mParentSystem( pParentSystem )
      , mID(ID)
    {
         mParentSystem->LogMessage( "Creating... "+mID );
    }

    ~Subsystem()
    {
         mParentSystem->LogMessage( "Destroying... "+mID );
    }

    // implements pure virtual functions from SubsystemBase
    void DoSomething(void) { mParentSystem->LogMessage( mID + " is DoingSomething (tm)."); }
    int GetSize(void) { return sizeof(Subsystem); }

  private:
    System * mParentSystem; // raw pointer to avoid cycles - can also use weak_ptrs
    std::string mID;
};



//////////////////////////////////////////////////////////////////
// Actual Use Example
int main(int argc, char* argv[])
{

  std::cout << "main(): Creating Handles H1 and H2 for Subsystems. " << std::endl;
  System::SubsystemHandle H1;
  System::SubsystemHandle H2;

  std::cout << "-------------------------------------------" << std::endl;
  {
    std::cout << "  main(): Begin scope for System." << std::endl;
    System mySystem;
    int FrankIndex = mySystem.AddSubsystem(new Subsystem(&mySystem, "Frank"));
    int ErnestIndex = mySystem.AddSubsystem(new Subsystem(&mySystem, "Ernest"));

    std::cout << "  main(): Assigning Subsystems to H1 and H2." << std::endl;
    H1=mySystem.GetSubsystem(FrankIndex);
    H2=mySystem.GetSubsystem(ErnestIndex);


    std::cout << "  main(): Doing something on H1 and H2." << std::endl;
    H1->DoSomething();
    H2->DoSomething();
    std::cout << "  main(): Leaving scope for System." << std::endl;
  }
  std::cout << "-------------------------------------------" << std::endl;
  std::cout << "main(): Doing something on H1 and H2. (outside System Scope.) " << std::endl;
  H1->DoSomething();
  H2->DoSomething();
  std::cout << "main(): No errors from using handles to out of scope Subsystems because of Proxy to Null Object." << std::endl;

  return 0;
}

Вывод из кода:

main(): Creating Handles H1 and H2 for Subsystems.
-------------------------------------------
  main(): Begin scope for System.
  <System>: System is constructing.
  <System>: Creating... Frank
  <System>: Adding Subsystem:
  <System>: Creating... Ernest
  <System>: Adding Subsystem:
  main(): Assigning Subsystems to H1 and H2.
  main(): Doing something on H1 and H2.
  <System>: Frank is DoingSomething (tm).
  <System>: Ernest is DoingSomething (tm).
  main(): Leaving scope for System.
  <System>: System is going out of scope.
  <System>: Destroying... Frank
  <System>: Destroying... Ernest
-------------------------------------------
main(): Doing something on H1 and H2. (outside System Scope.)
main(): No errors from using handles to out of scope Subsystems because of Proxy to Null Object.

Другие мысли:

  • Интересная статья, которую я прочитал в одной из книг по игровому программированию Gems, рассказывает об использовании нулевых объектов для отладки и разработки.В частности, они говорили об использовании нулевых графических моделей и текстур, таких как текстура шахматной доски, чтобы недостающие модели действительно выделялись.То же самое можно было бы применить и здесь, изменив NullSubsystem для ReportingSubsystem который регистрировал бы вызов и, возможно, стек вызовов всякий раз, когда к нему обращаются.Это позволило бы вам или клиентам вашей библиотеки отслеживать, где они находятся, в зависимости от чего-то, что вышло за рамки, но без необходимости вызывать сбой.

  • Я упомянул в комментарии @Arkadiy, что циклическая зависимость, которую он поднял между System и Subsystem это немного неприятно.Это можно легко исправить, имея System производный от интерфейса, на котором Subsystem зависит от приложения Роберта К. Мартина Принцип Инверсии зависимостей.Еще лучше было бы изолировать функциональность, которая Subsystemим нужно получить информацию от своего родителя, написать для этого интерфейс, а затем обратиться к разработчику этого интерфейса в System и передайте это в Subsystems, который удерживал бы его через shared_ptr.Например, у вас может быть LoggerInterface, который ваш Subsystem используется для записи в журнал, тогда вы могли бы получить CoutLogger или FileLogger из него, и сохраните экземпляр такого в System.
    Eliminating the Circular Dependency

Другие советы

Это выполнимо при правильном использовании weak_ptr класс.На самом деле, вы уже довольно близки к тому, чтобы найти хорошее решение.Вы правы в том, что нельзя ожидать, что вы "перехитрите" своих клиентских программистов, и вам не следует ожидать, что они всегда будут следовать "правилам" вашего API (как я уверен, вы уже знаете).Итак, лучшее, что вы действительно можете сделать, - это контролировать ущерб.

Я рекомендую вам позвонить по адресу GetSubsystem вернуть a weak_ptr вместо того, чтобы shared_ptr просто для того, чтобы клиентский разработчик мог проверить достоверность указателя, не всегда требуя ссылки на него.

Аналогично, иметь pParentSystem быть boost::weak_ptr<System> чтобы он мог внутренне определить, является ли его родительский System все еще существует через вызов lock вкл . pParentSystem вместе с чеком на NULL (необработанный указатель не скажет вам этого).

Предполагая, что вы измените свой Subsystem класс, чтобы всегда проверять, соответствует ли он System объект существует, вы можете гарантировать, что если клиентский программист попытается использовать Subsystem объект за пределами предполагаемой области, результатом которого будет ошибка (которую вы контролируете), а не необъяснимое исключение (которое вы должны доверить программисту-клиенту для перехвата / правильной обработки).

Итак, в вашем примере с main(), - ничего не взорвется!Самый изящный способ справиться с этим в Subsystemлучше всего было бы, чтобы это выглядело примерно так:

class Subsystem
{
...
  ~Subsystem() {
       boost::shared_ptr<System> my_system(pParentSystem.lock());

       if (NULL != my_system.get()) {  // only works if pParentSystem refers to a valid System object
         // now you are guaranteed this will work, since a reference is held to the System object
         my_system->LogMessage( "Destroying..." );
       }
       // Destroy this subsystem: deallocate memory, release resource, etc.             

       // when my_system goes out of scope, this may cause the associated System object to be destroyed as well (if it holds the last reference)
  }
...
};

Я надеюсь, что это поможет!

Здесь Система явно владеет подсистемами, и я не вижу смысла в совместном владении.Я бы просто вернул необработанный указатель.Если Подсистема переживает свою Систему, это само по себе является ошибкой.

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

Так почему же вы не использовали scoped_ptr ?Это, безусловно, будет связано с тем, что вы хотели, чтобы преимущества weak_ptr защищали от висячих ссылок, но вы можете указать weak_ptr только на shared_ptr.Итак, вы приняли общепринятую практику целесообразного объявления shared_ptr, когда то, что вы действительно хотели, было единоличным владением.Это ложное объявление, и, как вы говорите, оно ставит под угрозу вызов деструкторов в правильной последовательности.Конечно, если вы никогда не будете делиться правами собственности, вам это сойдет с рук, но вам придется постоянно проверять весь свой код, чтобы убедиться, что он никогда не был передан другим пользователям.

Что еще хуже, boost::weak_ptr неудобен в использовании (у него нет оператора ->), поэтому программисты избегают этого неудобства, ложно объявляя ссылки на пассивное наблюдение как shared_ptr .Это, конечно, общее право собственности, и если вы забудете обнулить этот shared_ptr, то ваш объект не будет уничтожен или его деструктор не будет вызван, когда вы намереваетесь это сделать.

Короче говоря, вы были обмануты библиотекой boost - она не учитывает хорошие методы программирования на C ++ и заставляет программистов делать ложные объявления, чтобы попытаться извлечь из этого какую-то выгоду.Это полезно только для написания связующего кода, который действительно хочет совместного владения и не заинтересован в жестком контроле над памятью или вызове деструкторов в правильной последовательности.

Я прошел по тому же пути, что и ты.Защита от висячих указателей крайне необходима в C ++, но библиотека boost не предоставляет приемлемого решения.Я должен был решить эту проблему - мой отдел программного обеспечения хотел получить гарантии того, что C ++ можно сделать безопасным.Поэтому я сделал свой собственный - это было довольно много работы, и его можно найти по адресу:

http://www.codeproject.com/KB/cpp/XONOR.aspx

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

К сожалению, программисты соблазнились сборкой мусора и решениями smart pointer "один размер подходит всем" и в значительной степени даже не думают о владельце и пассивных наблюдателях - в результате они даже не знают, что то, что они делают, неправильно, и не жалуются.Ересь против Boost - это почти неслыханно!

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

Я не вижу проблемы в том, что System::GetSubsystem возвращает необработанный указатель (а не интеллектуальный указатель) на подсистему.Поскольку клиент не несет ответственности за создание объектов, то не существует неявного контракта, по которому клиент несет ответственность за очистку.И поскольку это внутренняя ссылка, разумно предположить, что время жизни объекта Подсистемы зависит от времени жизни Системного объекта.Затем вы должны подкрепить этот подразумеваемый контракт документацией, подтверждающей это.

Дело в том, что вы не переназначаете и не разделяете права собственности - так зачем же использовать интеллектуальный указатель?

Настоящая проблема здесь - в вашем дизайне.Хорошего решения не существует, потому что модель не отражает хороших принципов дизайна.Вот удобное эмпирическое правило, которым я пользуюсь:

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

Я понимаю, что ваш пример надуман, но я часто вижу его антипаттерн в работе.Спросите себя, что такое ценность System добавляя , что std::vector< shared_ptr<SubSystem> > не так ли?Пользователи вашего API должны знать интерфейс SubSystem (поскольку вы их возвращаете), поэтому написание держателя для них только усложняет задачу.По крайней мере, люди знают интерфейс для std::vector, заставляя их вспомнить GetSubsystem() выше at() или operator[] это просто подлый.

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

Если вы собираетесь создать холдер, он должен скрыть сложность от ваших пользователей и избавить их от бремени, с которым вы можете справиться самостоятельно.В качестве примера, интерфейс, состоящий из а) Отправки команды подсистеме (например,a URI - /system /subsystem /command?param=value) и b) выполнять итерации подсистем и команд подсистемы (с помощью итератора, подобного stl) и, возможно, c) подсистема регистрации позволила бы вам скрыть почти все детали вашей реализации от ваших пользователей и обеспечить соблюдение требований к сроку службы / упорядочиванию / блокировке внутри.

Итеративный / перечислимый API в любом случае значительно предпочтительнее предоставления объектов - команды / регистрации могут быть легко сериализованы для создания тестовых примеров или файлов конфигурации, и они могут отображаться интерактивно (скажем, в элементе управления деревом, с диалоговыми окнами, составленными путем запроса доступных действий / параметров).Вы также защитили бы пользователей вашего API от внутренних изменений, которые вам, возможно, потребуется внести в классы подсистем.

Я бы предостерег вас от следования совету, содержащемуся в ответе Ааронса.Разработка такого простого решения проблемы, для реализации которого требуется 5 различных шаблонов проектирования, может означать только то, что решается неправильная проблема.Я также устал от всех, кто цитирует мистера Майерса в связи с дизайном, поскольку, по его собственному признанию:

"Я не писал производственного программного обеспечения более 20 лет, и я никогда не писал производственного программного обеспечения на C ++.Нет, никогда.Более того, я никогда даже не пытался писать производственное программное обеспечение на C ++, так что я не только не настоящий разработчик C ++, но даже не подражатель.Это немного уравновешивает тот факт, что я действительно писал исследовательское программное обеспечение на C ++ во время учебы в аспирантуре (1985-1993), но даже это было небольшим (несколько тысяч строк) материалом для одного разработчика, который можно было быстро выбросить.И с тех пор, как более десятка лет назад я начал работать консультантом, мое программирование на C ++ ограничивалось игрушечными программами типа “давайте посмотрим, как это работает” (или, иногда, “давайте посмотрим, сколько компиляторов это ломает”), обычно программами, которые умещаются в одном файле".

Нельзя сказать, что его книги не стоят прочтения, но у него нет полномочий говорить о дизайне или сложности.

В вашем примере было бы лучше, если бы Система содержала vector<Subsystem> вместо того, чтобы vector<shared_ptr<Subsystem> >.Это и проще, и избавляет вас от беспокойства, которое у вас есть.Вместо этого GetSubsystem вернет ссылку.

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

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

Если бы каждый из объектов подсистемы выполнял addref для системного объекта при создании и release при уничтожении, вы могли бы, по крайней мере, отобразить исключение, если счетчик ссылок был неверным при досрочном уничтожении системного объекта.

Использование weak_ptr также позволило бы вам вместо этого предоставлять сообщение, а также взрываться, когда объекты освобождаются в неправильном порядке.

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

Было предложено по крайней мере два хороших решения, поэтому я не буду пытаться превзойти предыдущие постеры.Я могу только отметить, что в решении @Aaron у вас может быть прокси для Системы, а не для подсистем - в зависимости от того, что сложнее и что имеет смысл.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top