문제

나는 지난 2년 동안 내 프로젝트에서 스마트 포인터(정확하게는 Boost::shared_ptr)를 광범위하게 사용해 왔습니다.나는 그들의 이점을 이해하고 감사하며 일반적으로 그들을 많이 좋아합니다.그러나 더 많이 사용할수록 프로그래밍 언어에서 좋아하는 메모리 관리 및 RAII와 관련된 C++의 결정적 동작이 더 그리워집니다.스마트 포인터는 메모리 관리 프로세스를 단순화하고 무엇보다도 자동 가비지 수집을 제공하지만 문제는 일반적으로 자동 가비지 수집을 사용하고 스마트 포인터를 사용하면 (비)초기화 순서에 따라 어느 정도 불확정성이 발생한다는 것입니다.이러한 비결정론은 프로그래머의 통제권을 빼앗고, 제가 최근에 깨달은 것처럼 API를 설계하고 개발하는 작업을 만듭니다. 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. 의 클라이언트 SubsystemS를 알아야합니다 Subsystem 그들은 사용중인 것이 유효합니다.

처리 #1

System the SubsystemS 및 자체 범위로 수명주기를 관리해야합니다. 사용 shared_ptrS는 파괴를 단순화하기 때문에 특히 유용하지만, 거래와 관련하여 추구하는 결정론을 잃어 버리기 때문에 나눠서는 안됩니다.

처리 #2

이것은 해결하기위한 더 굳은 관심사입니다. 문제를 더 자세히 설명하려면 클라이언트가 Subsystem 그 동안 Subsystem (그리고 부모입니다 System) 존재하지만 a Subsystem 파괴됩니다.

이것은의 조합으로 쉽게 해결됩니다 프록시 패턴,, 상태 패턴 그리고 널 객체 패턴. 이것은 해결책의 약간 복잡한 것처럼 보일 수 있습니다. '복잡성의 반대편에는 단순성이 있습니다.. ' 라이브러리/API 개발자로서 시스템을 강력하게 만들기 위해 추가 마일을 가야합니다. 또한, 우리는 시스템이 사용자가 기대하는대로 직관적으로 행동하고 오용하려고 할 때 우아하게 부패하기를 원합니다. 이 문제에 대한 많은 해결책이 있지만,이 문제는 당신과 그와 같이 모든 중요한 지점으로 당신을 데려 가야합니다. Scott Meyers "올바르게 사용하기 쉽고 잘못 사용하기가 어렵습니다.'

자, 나는 실제로 그것을 가정하고 있습니다. System 일부 기본 클래스에서 거래됩니다 Subsystem당신은 다양한 다른 것을 도출하는 s입니다 Subsystem에스. 아래에서 그것을 소개했습니다 SubsystemBase. 당신은 소개해야합니다 대리 물체, SubsystemProxy 아래의 인터페이스를 구현하는 아래 SubsystemBase 객체에 요청을 전달함으로써 프록시가됩니다. (이런 의미에서, 그것은 특별한 목적 적용과 매우 흡사합니다. 데코레이터 패턴.) 각 Subsystem 이 객체 중 하나를 생성하고 shared_ptr, 요청할 때 반환합니다 GetProxy(), 부모가 불러옵니다 System 언제 GetSubsystem() 호출됩니다.

A. System 범위를 벗어나면 각각이 다릅니다 Subsystem 물체가 파괴됩니다. 그들의 파괴자에서 그들은 부릅니다 mProxy->Nullify(), 그것은 그들의 원인입니다 대리 그들의 대상을 바꾸는 대상 상태. 그들은 a를 가리키기 위해 바꾸어 이것을합니다 널 객체, 그것을 구현합니다 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.

다른 생각들:

  • 게임 프로그래밍 보석 중 하나에서 읽은 흥미로운 기사는 디버깅 및 개발을 위해 NULL 객체를 사용하는 것에 대해 이야기합니다. 그들은 누락 된 모델이 실제로 눈에 띄게 만들기 위해 체커 보드 텍스처와 같은 NULL 그래픽 모델 및 텍스처를 사용하는 것에 대해 구체적으로 이야기했습니다. 변경하여 여기에 동일하게 적용 할 수 있습니다. NullSubsystem a ReportingSubsystem 통화를 기록하고 액세스 할 때마다 콜 스택을 기록합니다. 이를 통해 귀하 또는 귀하의 도서관의 고객은 범위를 벗어난 것에 따라 사고를 일으킬 필요가없는 곳을 추적 할 수 있습니다.

  • 나는 의견 @arkadiy가 그가 제기 한 원형 의존성을 언급했다. System 그리고 Subsystem 약간 불쾌합니다. 쉽게 치료할 수 있습니다 System 인터페이스에서 파생하십시오 Subsystem Robert C Martin의 적용에 따라 다릅니다 의존성 반전 원리. 여전히 기능을 분리하는 것이 좋습니다 Subsystem부모님이 필요로하고 인터페이스를 작성한 다음 해당 인터페이스의 구현자를 System 그리고 그것을 전달합니다 SubsystemS, a를 통해 보관합니다 shared_ptr. 예를 들어, 당신은 가질 수 있습니다 LoggerInterface, 당신의 Subsystem 로그에 쓰는 데 사용하면 도출 할 수 있습니다. CoutLogger 또는 FileLogger 그것으로부터, 그리고 그러한 인스턴스를 유지하십시오 System.
    Eliminating the Circular Dependency

다른 팁

이는 다음을 적절히 사용하면 가능합니다. weak_ptr 수업.사실, 당신은 이미 좋은 해결책을 찾는 데 꽤 가까워졌습니다.클라이언트 프로그래머를 "뛰어나도록" 기대하거나 그들이 항상 API의 "규칙"을 따를 것이라고 기대해서는 안 된다는 점은 맞습니다(당신도 이미 알고 있다고 확신합니다).따라서 실제로 할 수 있는 최선의 방법은 피해를 통제하는 것입니다.

다음으로 전화해 보시기 바랍니다. GetSubsystem 반환하다 weak_ptr 보다는 shared_ptr 클라이언트 개발자가 포인터에 대한 참조를 항상 요구하지 않고도 포인터의 유효성을 테스트할 수 있도록 하기 위한 것입니다.

마찬가지로 pParentSystem 가 되다 boost::weak_ptr<System> 내부적으로 상위 항목인지 여부를 감지할 수 있도록 System 호출을 통해 여전히 존재합니다. lock ~에 pParentSystem 수표와 함께 NULL (원시 포인터는 이를 알려주지 않습니다).

당신이 당신을 변경한다고 가정 Subsystem 해당 클래스인지 여부를 항상 확인하는 클래스 System 개체가 존재하는 경우 클라이언트 프로그래머가 해당 개체를 사용하려고 시도하는지 확인할 수 있습니다. Subsystem 설명할 수 없는 예외(클라이언트 프로그래머가 포착/적절하게 처리하도록 신뢰해야 함)가 아니라 오류가 발생하는(사용자가 제어하는) 의도된 범위 밖의 개체입니다.

따라서 귀하의 예에서 main(), 일이 잘 안 풀려 붐!이를 처리하는 가장 우아한 방법은 Subsystem의 dtor는 다음과 같이 보이도록 하는 것입니다.

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 ++ 코드)를 기반으로 한 디자인은 객체가 독점 소유권 포인터에 의해 유지되어야합니다. 부스트에서 그것은 scoped_ptr입니다.

그래서 왜 scoped_ptr을 사용하지 않았습니까? 약한 참조로부터 보호하기를 원했기 때문일 것입니다. 그래서 당신은 당신이 정말로 원하는 것이 단일 소유권 일 때 Shared_ptr을 편리하게 선언하는 일반적인 관행을 채택했습니다. 이것은 허위 선언이며, 당신이 말했듯이, 그것은 올바른 순서로 소멸되는 소멸자를 손상시킵니다. 물론 소유권을 공유하지 않으면 그와 함께 도망 갈 것입니다. 그러나 공유되지 않은지 확인하기 위해 모든 코드를 지속적으로 확인해야합니다.

문제를 악화시키기 위해 Boost :: excern_ptr은 사용하기에 불편 함 ( -> 운영자가 없음)이므로 프로그래머는 Shared_ptr로 수동적 관찰 참조를 거짓으로 선언함으로써 이러한 불편 함을 피합니다. 이것은 물론 소유권을 공유하며, 공유 _ptr을 널리 잊어 버린 경우, 당신의 대상은 파괴되지 않거나 의도 할 때 파괴자에게 전화를 걸지 않을 것입니다.

요컨대, 당신은 Boost 라이브러리에 의해 샤프트를 받았습니다. 좋은 C ++ 프로그래밍 관행을 수용하지 못하고 프로그래머가 그 혜택을 얻기 위해 허위 선언을하도록 강요하지 않습니다. 진정으로 공유 소유권을 원하고 메모리 나 소멸자에 대한 엄격한 제어에 관심이없는 접착제 코드를 스크립팅하는 데 유용합니다.

나는 당신과 같은 길을 따라갔습니다. C ++에서는 매달려있는 포인터에 대한 보호가 필요하지만 Boost 라이브러리는 허용되는 솔루션을 제공하지 않습니다. 이 문제를 해결해야했습니다. 소프트웨어 부서는 C ++가 안전해질 수 있다는 확신을 원했습니다. 그래서 나는 내 자신을 굴 렸습니다 - 그것은 꽤 많은 일이었고 다음에서 찾을 수 있습니다.

http://www.codeproject.com/kb/cpp/xonor.aspx

단일 스레드 작업에는 완전히 적합하며 스레드에서 공유되는 포인터를 수용하기 위해 업데이트하려고합니다. 주요 기능은 독점적으로 소유 한 객체의 Smart (Self Zeroing) 수동 관찰자를 지원한다는 것입니다.

불행히도 프로그래머는 쓰레기 수집에 유혹을 받았으며 '하나의 크기는 모든 스마트 포인터 솔루션에 적합하며 대부분 소유권과 수동 관찰자에 대해 생각조차하지 않습니다. 'T 불평. 부스트에 대한 이단은 거의 들어 본 적이 없습니다!

귀하에게 제안 된 솔루션은 터무니없이 복잡하고 전혀 도움이되지 않습니다. 그것들은 대상 포인터가 올바르게 선언 해야하는 뚜렷한 역할을 가지고 있으며, 맹목적인 믿음이 해결책이어야한다는 것을 인식하기위한 문화적 꺼리는 것에서 발생하는 부조리의 예입니다.

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는 어쨌든 객체를 노출하는 것보다 훨씬 바람직합니다. 테스트 케이스 또는 구성 파일을 생성하기 위해 명령/등록이 쉽게 직렬화 될 수 있으며 대화식으로 표시 될 수 있으며 (예 : 쿼리로 구성된 대화를 통해 트리 컨트롤에서 사용 가능한 조치/매개 변수). 또한 Subsystem 클래스에 필요한 내부 변경으로부터 API 사용자를 보호합니다.

Aarons 답변의 조언을 따르는 것에 대해주의를 기울일 것입니다. 문제에 대한 솔루션 설계이 단순한 5 가지 디자인 패턴이 구현 해야하는 간단한 문제는 잘못된 문제가 해결되고 있음을 의미 할 수 있습니다. 나는 또한 자신의 입학에 의해 디자인과 관련하여 마이어스 씨를 인용하는 사람에게도 지친다.

"저는 20 년 넘게 생산 소프트웨어를 작성하지 않았으며 C ++로 프로덕션 소프트웨어를 작성한 적이 없습니다. 아니요. C ++ Developer, 나는 지망도가 아닙니다. 이것을 약간 균형을 맞추는 것은 대학원 학년 (1985-1993) 동안 C ++에서 연구 소프트웨어를 작성했다는 사실입니다. 열심히 일하는 것입니다. 이것은”프로그램, 일반적으로 단일 파일에 맞는 프로그램을 중단합니다.

그의 책은 읽을 가치가 없다고 말할 수는 없지만 디자인이나 복잡성에 대해 말할 권한이 없습니다.

당신의 예에서는 시스템이 vector<Subsystem> a보다는 vector<shared_ptr<Subsystem> >. 둘 다 더 간단하고 관심을 제거합니다. GetSubsystem은 대신 참조를 반환합니다.

스택 객체는 인스턴스턴스가있는 곳에 반대 순서로 해제되므로 API를 사용하는 개발자가 스마트 포인터를 관리하려고하지 않는 한 일반적으로 문제가되지 않습니다. 당신이 예방할 수없는 것들이 있습니다. 당신이 할 수있는 최선은 런타임에 경고를 제공하는 것입니다.

귀하의 예는 COM과 매우 흡사합니다. Shared_PTR을 사용하여 반환되는 서브 시스템에 대한 참조 계산이 있지만 시스템 객체 자체에 누락됩니다.

각 하위 시스템 객체가 생성시 시스템 객체에 대한 추가 사항을 사용하고 파괴에 대한 릴리스가 있으면 시스템 객체가 일찍 파괴 될 때 참조 수가 잘못된 경우 최소한 예외를 표시 할 수 있습니다.

약한 _ptr을 사용하면 잘못된 순서로 물건이 해제 될 때 대신 메시지를 날려 버릴 수 있습니다.

문제의 본질은 순환 참조입니다.시스템은 하위 시스템을 나타내고 하위 시스템은 시스템을 나타냅니다.이러한 종류의 데이터 구조는 참조 계산으로 쉽게 처리할 수 없습니다. 적절한 가비지 수집이 필요합니다.가장자리 중 하나에 원시 포인터를 사용하여 루프를 중단하려고 합니다. 이렇게 하면 더 많은 문제가 발생할 뿐입니다.

적어도 두 가지 좋은 해결책이 제안되었으므로 이전 포스터를 능가하려고 시도하지 않겠습니다.@Aaron의 솔루션에서는 무엇이 더 복잡하고 무엇이 의미가 있는지에 따라 하위 시스템이 아닌 시스템에 대한 프록시를 가질 수 있다는 점만 주목할 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top