作为分配的一部分,我将研究一个使用C++类的"两阶段"构造的开发工具包:
// Include Header
class someFubar{
public:
someFubar();
bool Construction(void);
~someFubar();
private:
fooObject _fooObj;
}
Run Code Online (Sandbox Code Playgroud)
在源头
// someFubar.cpp
someFubar::someFubar : _fooObj(null){ }
bool
someFubar::Construction(void){
bool rv = false;
this->_fooObj = new fooObject();
if (this->_fooObj != null) rv = true;
return rv;
}
someFubar::~someFubar(){
if (this->_fooObj != null) delete this->_fooObj;
}
Run Code Online (Sandbox Code Playgroud)
为什么会使用这种"两相",有什么好处?为什么不在实际构造函数中实例化对象初始化?
小智 6
没有充分的理由这样做 - 避免.该代码的原作者可能根本不知道他们在做什么.
当您必须为(您的类的)用户提供对资源分配/释放的更多控制时,这非常有用.例如,想一下Socket类.用户将主机和端口参数传递给构造函数,并且可能希望延迟套接字的实际"打开"(即,分配低级SOCKET对象)到以后的时间.他也可能希望随意关闭并重新打开Socket.两阶段构造(或延迟初始化)促进了这一点.这样的Socket接口看起来像:
class Socket
{
public:
Socket (const std::string& host, int port) : host_(host), port_(port), sock_(NULL) { }
~Socket () { close (); }
void open () throw (NetworkException&)
{
sock_ = new_low_level_socket (host_, port_);
}
void close ()
{
if (sock_)
{
close_low_level_socket (sock_);
sock_ = NULL;
}
}
// private members
};
// Usage:
ing
main ()
{
Socket sock ("www.someurl.com", 80);
sock.open ();
// do something with sock
sock.close ();
// do something else
sock.open();
// do something with sock
return 0;
// sock is closed by destructor.
}
Run Code Online (Sandbox Code Playgroud)
顺便说一句,这个习惯用法不是防止从构造函数中抛出异常的替代品.如果构造函数失败,则抛出异常.有关详细信息,请参阅此BS常见问题解答以及C++ - FAQ-Lite中的条目.
为什么要使用这种“两相”,有什么好处?
使用这个习语有两个原因:
在异常的使用在语言中标准化之前,过去曾使用过这个习语,并且被不理解(或出于某种原因不想使用)异常的库设计者使用。
您仍然会在遗留/向后兼容的代码(例如 MFC)中找到它。
Construction成员函数声明为(纯)虚拟的,并允许专门的类来替换它。这几乎总是 [1] 一个标志或糟糕的设计(你可以并且应该实现你的代码,这样你就不需要这个习惯用法了)。[1] - “几乎总是”在这里意味着我完全没有理由这样做,但我可能会遗漏一些东西:)。
我从沟渠中得到的答案是两阶段结构 - 特别是在处理硬件初始化时 - 是必须的.在现实世界中,如果某个对象无法构建或控制某些东西 - 那么噩梦般的场景就等待发生.
如果硬件控制类在其构造函数中初始化硬件(附加到可能弯曲,折叠,碾压或毁坏人类的东西),那么应用程序的整个架构必须改变以适应它.它改变了,因为我无法以我想要的方式构建我的对象.也许我想要一个嵌入所有子控制器的"主控制器".我希望随时自由地构建它们.在构造中可能无法构造或激活硬件的物体会在工作中抛出巨大的扳手.在这些情况下,我必须推迟创建对象,直到正确的时间.这意味着指向对象的指针 - 它们可能是null,并且强制您处理指向大型重要对象的空指针.我讨厌被迫这样做.
在我的世界观中,我希望我的构造函数是无害的,总是可以正常工作,简单地初始化它们的所有属性(并且可能对它们应用一些参数) - 而不是别的.只需准备对象以便我可以构造它们,或者随时随地对它们应用引用.我会处理我选择的大重要的部分在同一时间"连接"或"初始化"和地点,最好有一个开放的(...)或init(...)方法.