对C++中成员对象构造的更多控制

More control over construction of member objects in C++

本文关键字:控制 C++ 成员对象      更新时间:2023-10-16

我有几个类,在这些类中,我需要对成员构造进行比初始化列表更多的控制。

在一个类中,如果原始构造抛出,我需要能够重试具有不同参数的成员的构造。

在第二个类中,我需要将成员对象的地址传递给另一个函数,该函数将依次调用构造函数(通过placementnew)。这里的问题是这个函数是一个黑盒,我不知道它会传递给构造函数什么。

在这两种情况下,都要求成员对象保持在包含对象中,而不是动态分配。

实现这一目标的最佳方法是什么?

编辑

我注意到一个问题"是否可以将成员初始化推迟到构造函数主体?",该问题建议boost::optional。这并没有完全解决我的问题,原因有几个。首先,我不想延迟初始化,只想对它有更多的控制。其次,boost::optional存储一个额外的bool来指示对象是否初始化,这在我的情况下是不需要的。然而,它确实让我思考,我想出了我在下面发布的解决方案。

不能仅通过构造函数"重试"构造。您可以使用try/catch吞下/处理构造函数内引起的异常,如果构造函数内的代码失败,则可以继续填充对象(使用简单的赋值)。

如果初始化列表抛出,则表示您。。。搞砸了?不过,初始化列表中不应该抛出任何内容。它是在你的构造函数主体之前执行的,你不能控制其中抛出的异常

如果您确实需要使用不同的参数重试构造,请将构造封装在工厂函数中,该函数可以捕获异常并在收到异常时尝试不同的选项。

继续…当你调用构造函数时,你就调用了构造函数。构造函数本身根据是否使用动态分配机制(主要是新的)在堆或堆栈上创建对象。您不能在构造函数主体本身内决定在其他地方声明对象,因为已经做出了决定。当你调用new时,你必须直接使用placementnew来实现这一点。

将构造和初始化过程分开。我知道这听起来可能很奇怪,但在初始化列表中构造基本对象,然后尝试/catch用分离的init()方法在构造函数的主体中用不同的参数集初始化它。

我最终创建了以下类:

#include <type_traits> // Or <boost/type_traits.hpp>
template <typename Ty>
class manually_constructed
{
public:
   template <typename T>
   manually_constructed(T construct_func) {
      construct_func(static_cast<Ty*>(static_cast<void*>(&data)));
   }
   ~manually_constructed() {
      static_cast<Ty*>(static_cast<void*>(&data))->~Ty();
   }
   Ty& operator*() {
      return *static_cast<Ty*>(static_cast<void*>(&data));
   }
   Ty* operator->() {
      return static_cast<Ty*>(static_cast<void*>(&data));
   }
private:
   // Replace 'std' with 'boost' if your standard library doesn't support
   // type_traits, yet.
   std::aligned_storage<sizeof(Ty), std::alignment_of<Ty>::value>::type data;
};

然后,在我的类中,我可以有一个成员,如manually_constructed<OtherClass> object和一个私有静态方法,如:

static void construct_object(OtherClass *p) {
   try {
      new(p) OtherClass(/* Risky arguments */);
   } catch(...) {
      new(p) OtherClass(/* Fallback arguments */);
   }
}

最后,在我的初始值设定项列表中,我可以有object(construct_object)

有了这个解决方案,所有成员对象都存储在包含对象中,没有额外的空间开销,所有的成员对象都按照正确的顺序构建,手动构建的对象将自动销毁。