C ++中的两阶段构造

时间:2010-06-11 09:11:33

标签: c++ sdk

作为一项任务的一部分,我将研究一个使用C ++类的“两阶段”构造的开发工具包:

// Include Header
class someFubar{
public:
    someFubar();
    bool Construction(void);
    ~someFubar();
private:
    fooObject _fooObj;
}

在源

// 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;
}

为什么要使用这种“两相”,有什么好处?为什么不在实际构造函数中实例化对象初始化?

10 个答案:

答案 0 :(得分:18)

关于Two Phase Construction

的文件

这个想法是你不能从构造函数返回一个值来指示失败。指示构造函数失败的唯一方法是抛出异常。这并不总是令人满意,尤其是因为异常安全是一个非常复杂的话题。

因此,在这种情况下,构造被拆分:一个不抛出,但也没有完全初始化的构造函数,以及一个执行初始化的函数,可以返回成功或失败的指示,而不必(必然)抛出异常。

答案 1 :(得分:6)

没有充分理由这样做 - 避免。该代码的原作者可能根本不知道他们在做什么。

答案 2 :(得分:5)

当您必须为(您的类的)用户提供对资源分配/释放的更多控制时,这非常有用。例如,想一下Socket类。用户将主机和端口参数传递给构造函数,并且可能希望延迟套接字的实际“打开”(即,分配低级SOCKET对象)到以后的时间。他也可能希望随意关闭并重新打开Socket。两阶段构造(或Lazy Initialization)促进了这一点。这样的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.
}
顺便说一句,这个习惯用法并不能防止从构造函数中抛出异常。如果构造函数失败,则抛出异常。有关详情,请参阅this BS FAQthe entry at C++-FAQ-Lite

答案 3 :(得分:5)

我从战壕中得到的答案是两阶段结构 - 特别是在处理硬件初始化时 - 是必须的。在现实世界中,如果某个对象无法构造或控制某些东西 - 那么噩梦般的场景就等待发生。

如果硬件控制类在其构造函数中初始化硬件(附加到可能弯曲,折叠,主轴或肢解人类的东西),那么应用程序的整个架构必须改变以适应它。它改变了,因为我无法以我想要的方式构建我的对象。也许我想要一个嵌入所有子控制器的“主控制器”。我希望随时自由地构建它们。在构造中可能无法构造或激活硬件的物体会在工作中抛出巨大的扳手。在这些情况下,我必须推迟创建对象,直到正确的时间。这意味着指向对象的指针 - 它们可能是null,并且强制您处理指向大型重要对象的空指针。我讨厌被迫这样做。

在我的世界观中,我希望我的构造函数是无害的,总是可以正常工作,简单地初始化它们的所有属性(并且可能对它们应用一些参数) - 而不是别的。只需准备对象以便我可以构造它们,或者随时随地对它们应用引用。我将在我选择的时间和地点处理重要部分的“连接”或“初始化”,最好使用开放(...)或init(...)方法。

答案 4 :(得分:4)

  

为什么会使用这种“两阶段”,有什么好处?

这个习惯用法有两个原因:

  • 将初始化分成两个不同的部分:始终稳定且不会失败的部分(在构造函数中保留)和可能失败的部分(单独的构造函数)。

这个习惯用法在过去使用过,然后才使用语言标准化异常,以及那些不理解(或出于某种原因不想使用)异常的图书馆设计师。

您仍会在传统/向后兼容代码(例如MFC)中找到它。

  • 实施“虚拟建筑”。基本上,您将Construction成员函数声明为(纯)虚拟,并允许专门的类替换它。这几乎总是[1]一个标志或糟糕的设计(你可以而且应该实现你的代码,这样你就不需要这个习语)。

[1] - “几乎总是”在这里意味着我完全没有理由去做,但我可能会遗漏一些事情:)。

答案 5 :(得分:2)

有时您必须使用C ++而不启用异常。在这种情况下,构造函数缺少返回值会使生活变得困难,因此您可以使用明确的构造函数来替代成功。

答案 6 :(得分:1)

给出的来源极端奇怪。

如果它是C ++,那么new不能返回0,并且应该抛出异常。

如果它不完全不同于C ++,其中new在失败时返回0(已知存在这样的事物),那么Construction相当于:

bool someFubar::Construction(){ // no need for void in C++, it's not C
    delete _fooObj;             // nothing stops this getting called twice!
    _fooObj = new fooObject();  // no need for this->, it's not Python
    return _fooObj;             // non-zero is true 
}

someFubar::~someFubar(){
    delete fooObj;              // it's OK to delete NULL in C++. 
}

现在我们遇到了一个问题 - 如果构造被调用两次,我们是否再次构造它并如上所述返回true,不再构造它并在构造时返回true,或者不再构造它并返回false作为构建一些东西是错误的吗?

有时 - 例如,对于管理外部资源的类 - 您可能希望将它们实现为您创建的状态机,然后分配资源。这在资源可能变为无效的情况下是合理的,因此您无论如何都必须检查对其执行的操作。提供从类型到bool的转换是在标准库中使用此类资源的模式。通常你应该一次构造一个对象并期望它是有效的。

答案 7 :(得分:1)

执行此操作的原因可能是构造函数不返回任何值。有些人喜欢在对象实例化之后执行创建之类的函数。如果您没有使用异常,因为它们会创建大量代码(特别是在嵌入式世界中),则不可能只使用构造函数,因为它不提供任何保证(就像我说的,它不能返回任何值)。

我看到的其他技术是这样的:

XObject* o = new XObject();
o->IsOk();

它基本上构造构造函数内部的构造并将操作的结果保存在变量中 - 而不是节省空间。

答案 8 :(得分:1)

其他人在这里提到了Construction()方法是虚拟的情况。这可以发挥有用作用的一种情况是对象图的反序列化(RogueWave Tools.h ++使用了这种技术IIRC):

  • 工厂根据其类型的一些动态表示来分配具体的子类 - 可能是通过开关,数组或某种类型的字典间接指示 - 于是......
  • ...它立即转身并要求新构造的对象反序列化自己的内容 - 因此只恢复它自己的状态,因为只有知道如何 - 通过调用虚拟构造()方法,也许传递输入流或任何要读取的内容。

这种技术的又一个用途被描述为here:作为从多个重载构造函数中分解公共代码的一种方法。

答案 9 :(得分:0)

我当然不会成为这两步建设的粉丝。还有许多其他方法可以在构造函数中引发异常/错误。首先,任何值得它重量的C ++编译器应该能够从构造函数中抛出异常并允许它被恰当地捕获。

另一种解决方案是使用类似于posix errno 方法的内部错误代码。然后调用者可以通过调用该类的成员来查询此错误代码。 IIR Windows与 GetLastError 函数类似。