作者热门文章
- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
在过去的两年里,我一直在我的项目中广泛使用智能指针(准确地说是 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
};
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;
}
最佳答案
问题总结
这个问题有两个相互矛盾的问题。
Subsystem
的生命周期管理s,允许他们在正确的时间移除。 Subsystem
的客户需要知道Subsystem
他们使用的是有效的。 System
拥有
Subsystem
s 并且应该用它自己的范围来管理他们的生命周期。使用
shared_ptr
s 因为这特别有用,因为它简化了销毁,但是您不应该分发它们,因为那样您会失去您正在寻求的关于它们的解除分配的确定性。
Subsystem
的对象。而那
Subsystem
(并且它的父级
System
)存在,但在
Subsystem
之后表现得很好被摧毁。
System
交易
Subsystem
的一些基类s,你从中衍生出各种不同的
Subsystem
s。我在下面介绍它为
SubsystemBase
.您需要介绍一个
代理对象,
SubsystemProxy
下面,实现了
SubsystemBase
的接口(interface)通过将请求转发到它所代理的对象。 (从这个意义上说,它很像
Decorator Pattern 的特殊用途应用程序。)每个
Subsystem
创建这些对象之一,它通过
shared_ptr
持有。 , 并在通过
GetProxy()
请求时返回,由父级
System
调用对象时
GetSubsystem()
叫做。
System
超出范围,每个都是
Subsystem
对象被破坏。在他们的析构函数中,他们调用
mProxy->Nullify()
,这会导致他们的
代理对象更改其
国家 .他们通过更改为指向
来做到这一点。空对象 ,它实现了
SubsystemBase
界面,但什么都不做。
Subsystem
存在。此外,它不需要检查指针或保留应该被销毁的实例。
Subsystem
已被删除。
NullSubsystem
类,因为您只需要一个是有道理的。
ProxyableSubsystemBase
从
Subsystem
中完全抽象出代理行为,让它对这种行为一无所知。这是类的UML图:
#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.
其他想法:
NullSubsystem
也可以在此处应用相同的方法。对于 ReportingSubsystem
每当访问它时,它都会记录调用和可能的调用堆栈。这将允许您或您图书馆的客户根据超出范围的内容追踪他们所在的位置,而无需导致崩溃。System
之间提出的循环依赖和 Subsystem
有点不愉快。它可以通过 System
轻松解决。派生自 Subsystem
的接口(interface)取决于,Robert C Martin 的应用 Dependency Inversion Principle .更好的做法是隔离 Subsystem
的功能。需要他们的父级,为此编写一个接口(interface),然后在 System
中保留该接口(interface)的实现者并将其传递给 Subsystem
s,它将通过 shared_ptr
保存它.例如,您可能有 LoggerInterface
, 您的 Subsystem
用于写入日志,则可以导出 CoutLogger
或 FileLogger
从它,并在 System
中保留一个这样的实例.关于c++ - 关于智能指针及其不可避免的非决定论的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/400993/
在 C# 中有什么方法可以将属性标记为确定性的? 我问的原因是我经常发现自己声明一个局部变量并将属性读入其中,而不是多次访问该属性。 有什么方法可以将属性修饰为确定性的,以便编译器可以优化对该属性的多
我是一名优秀的程序员,十分优秀!