C++ 关于智能指针及其不可避免的不确定性的问题

C++ 关于智能指针及其不可避免的不确定性的问题,c++,shared-ptr,raii,object-lifetime,C++,Shared Ptr,Raii,Object Lifetime,在过去的两年里,我一直在我的项目中广泛使用智能指针(确切地说是boost::shared_ptr)。我理解并欣赏他们的好处,我通常非常喜欢他们。但是我越多地使用它们,我就越不关心C++的确定行为,因为我对程序管理语言中的内存管理和RAII很感兴趣。智能指针简化了内存管理过程,并提供了自动垃圾收集等功能,但问题是,通常使用自动垃圾收集和智能指针会在(取消)初始化的顺序上引入某种程度的不确定性。这种不确定性夺走了程序员的控制权,而且,正如我最近意识到的那样,使得设计和开发API的工作变得非常耗时,因

在过去的两年里,我一直在我的项目中广泛使用智能指针(确切地说是boost::shared_ptr)。我理解并欣赏他们的好处,我通常非常喜欢他们。但是我越多地使用它们,我就越不关心C++的确定行为,因为我对程序管理语言中的内存管理和RAII很感兴趣。智能指针简化了内存管理过程,并提供了自动垃圾收集等功能,但问题是,通常使用自动垃圾收集和智能指针会在(取消)初始化的顺序上引入某种程度的不确定性。这种不确定性夺走了程序员的控制权,而且,正如我最近意识到的那样,使得设计和开发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
};
类系统{
公众:
boost::shared_ptrGetSubsystem(无符号整数索引){
断言(索引LogMessage(“正在销毁…”);
//销毁此子系统:释放内存、释放资源等。
}
/*
这里还有其他东西
*/
私人:
System*pParentSystem;//避免循环的原始指针-也可以使用弱\u ptr
};
正如您已经知道的,子系统仅在系统的上下文中才有意义。但是,在这样的设计中,子系统可以很容易地比其父系统长寿

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;
}
intmain(){
{
boost::shared_ptrpSomeSubsystem;
{
boost::shared_ptrpSystem(新系统);
pSomeSubsystem=pSystem->GetSubsystem(/*一些索引*/);
}//我们的系统将超出范围并在此处被销毁,但pSomeSubsystem指向的子系统不会被销毁。
}//pSomeSubsystem将超出此范围,但请稍候,我们如何在子系统的析构函数中记录消息?!它的父系统毕竟已被破坏。轰!
返回0;
}
如果我们使用原始指针来保存子系统,那么当我们的系统崩溃时,我们就会破坏子系统,当然,pSomeSubsystem将是一个悬空的指针

虽然API设计师的工作不是保护客户端程序员不受自己的影响,但让API易于正确使用和不易错误使用是一个好主意。所以我问你们。你们认为呢?我应该如何缓解这个问题?你们将如何设计这样一个系统

提前感谢,,
Josh

在您的示例中,如果系统持有的是
向量,而不是
向量,那就更好了。这既简单又消除了您的顾虑。GetSubsystem将返回一个引用。

我认为System::GetSubsystem返回原始指针(而不是智能指针)没有问题对于子系统。由于客户端不负责构建对象,因此客户端不存在负责清理的隐式契约。而且由于它是一个内部引用,因此应该合理地假设子系统对象的生存期取决于系统对象的生存期。您应该hen通过文件说明,加强本默示合同


关键是,您没有重新分配或共享所有权-那么为什么要使用智能指针呢?

在这里,系统显然拥有子系统,我认为共享所有权没有任何意义。我只会返回一个原始指针。如果子系统比它的系统寿命长,这本身就是一个错误。

这可以通过正确使用
弱属性来实现r
class。事实上,您已经非常接近于拥有一个好的解决方案。您是对的,您不能期望您的客户程序员“超越”您的客户程序员,也不应该期望他们总是遵循API的“规则”(我相信您已经知道)。因此,您真正能做的最好的事情是损害控制

我建议您调用
GetSubsystem
返回
weak\u ptr
而不是
shared\u ptr
,这样客户端开发人员就可以测试指针的有效性,而不必总是声称引用它

类似地,让
pParentSystem
成为一个
boost::weak_ptr
,这样它就可以通过调用
pParentSystem
上的
lock
并检查
NULL
(原始指针不会告诉你这一点)在内部检测其父系统是否仍然存在

假设您将
子系统
类更改为始终检查其对应的
系统
对象是否存在,则可以确保如果客户端程序员尝试在预期范围之外使用
子系统
对象,则会导致错误(由您控制),而不是一个无法解释的异常(您必须相信客户端程序员能够捕获/正确处理)

因此,在您使用
main()
的示例中,事情不会一帆风顺!在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) } ... };
#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.