C++ 类:是否可以 select 构造函数主体中的成员构造函数? (无需双重初始化成员)
C++ classes: Is it possible to select a member's constructor in a constructor body? (Without doubly initializing the member)
考虑一下:
class Foo {
private:
Bar x;
public:
Foo(int a) { // no initialization here since constructor is dependent on a following if-block
if (a==0) x=Bar(12,'a', 34); // some (int, char, int) constructor of Bar
else x=Bar(13); // (int) constructor of Bar
}
}
这应该做的是检查参数 a 的值,并根据 a 的值使用特定构造函数和特定参数初始化 Bar x。然而,问题是,这当然会被编译器读取为
Foo(int a): Bar() { // !
if (a==0) x=Bar(12,'a', 34); // some Bar(int, char, int) constructor of Bar
else x=Bar(13); // Bar(int) constructor of Bar
}
编译器将 Bar() 添加到初始化列表中,因为我忽略了它。它现在被双重初始化,一次使用它的 () 构造函数,一次在函数体中。但是,如果初始化 Bar(即使使用其默认构造函数)的成本非常高(关于性能)并且我不能或不想进行这种双重初始化怎么办?
如何才能在实际的构造函数体中之前不初始化 Bar x?
如果这不可能,我将如何最好地解决这个问题?
补充一下:为什么 C++ 是这样设计的?为什么它强制在实际构造函数主体之前初始化成员?
不,你不能。保证在 ctor 主体中初始化成员。
整个初始化顺序明确定义为:
- 首先,最派生的class的构造函数调用虚拟基础class子对象的构造函数。虚拟基地 classes 以深度优先、从左到右的顺序进行初始化。
- 接下来,直接基础 class 子对象按照它们在 class 定义中声明的顺序构造。
- 接下来,(非静态)成员子对象按照它们的顺序构造
在 class 定义中声明。
- 最后,构造函数的主体被执行。
您可以将 x
设为 std::unique_ptr<Bar>
,然后在 ctor 中根据需要分配它。
C++ 是这样设计的,以便构造函数实际构造和初始化事物,而不是让你搬起石头砸自己的脚。
不过,自从 C++11 以来,现在有一种您可以使用的后门攻击技术:匿名联合的成员不会被构造,除非您显式构造它们。代码将是:
class Foo
{
union
{
Bar x;
};
public:
Foo(int a)
{
if (a==0)
new(&x) Bar(12,'a', 34);
else
new(&x) Bar(13);
}
~Foo()
{
x.~Bar();
}
};
需要明确的是,这应该是最后的手段。编译器生成的复制构造函数、移动构造函数等被定义为已删除,因此如果您希望这些操作可用,您也需要实现它们。
这是一个使用委托构造函数的解决方案:
class Foo
{
Bar b;
Foo( Bar b ): b(std::move(b)) { }
public:
Foo(int a): Foo( a ? Bar(13) : Bar(12, 'a', 34) ) {}
};
在委托期间创建所需的对象,然后调用采用 Bar
的构造函数。如果您想允许任何人从 Bar 构造 Foo ,那么您可以使该构造函数 public.
在内存方面比匿名联合解决方案稍微贵一些,但更不容易出错,并且不会产生额外的 copy/move 或动态分配。
class Foo {
private:
std::experimental::optional<Bar> x;
public:
Foo(int a) {
if (a==0) x.emplace(12,'a', 34); // some (int, char, int) constructor of Bar
else x.emplace(13); // (int) constructor of Bar
}
};
此外,在这种情况下 x
将像指针一样使用:x->some_Bar_function()
和 some_function_taking_a_Bar(*x)
。
我认为你问错了问题,与其试图禁止初始化,不如直接去做,即将你的 ctor 拼写为:
Foo(int a) : x((a==0) ? Bar(12,'a', 34) : Bar(13)) {}
这不会导致任何复制或移动(请参阅 here),并且它是惯用的。
考虑一下:
class Foo {
private:
Bar x;
public:
Foo(int a) { // no initialization here since constructor is dependent on a following if-block
if (a==0) x=Bar(12,'a', 34); // some (int, char, int) constructor of Bar
else x=Bar(13); // (int) constructor of Bar
}
}
这应该做的是检查参数 a 的值,并根据 a 的值使用特定构造函数和特定参数初始化 Bar x。然而,问题是,这当然会被编译器读取为
Foo(int a): Bar() { // !
if (a==0) x=Bar(12,'a', 34); // some Bar(int, char, int) constructor of Bar
else x=Bar(13); // Bar(int) constructor of Bar
}
编译器将 Bar() 添加到初始化列表中,因为我忽略了它。它现在被双重初始化,一次使用它的 () 构造函数,一次在函数体中。但是,如果初始化 Bar(即使使用其默认构造函数)的成本非常高(关于性能)并且我不能或不想进行这种双重初始化怎么办?
如何才能在实际的构造函数体中之前不初始化 Bar x? 如果这不可能,我将如何最好地解决这个问题?
补充一下:为什么 C++ 是这样设计的?为什么它强制在实际构造函数主体之前初始化成员?
不,你不能。保证在 ctor 主体中初始化成员。
整个初始化顺序明确定义为:
- 首先,最派生的class的构造函数调用虚拟基础class子对象的构造函数。虚拟基地 classes 以深度优先、从左到右的顺序进行初始化。
- 接下来,直接基础 class 子对象按照它们在 class 定义中声明的顺序构造。
- 接下来,(非静态)成员子对象按照它们的顺序构造 在 class 定义中声明。
- 最后,构造函数的主体被执行。
您可以将 x
设为 std::unique_ptr<Bar>
,然后在 ctor 中根据需要分配它。
C++ 是这样设计的,以便构造函数实际构造和初始化事物,而不是让你搬起石头砸自己的脚。
不过,自从 C++11 以来,现在有一种您可以使用的后门攻击技术:匿名联合的成员不会被构造,除非您显式构造它们。代码将是:
class Foo
{
union
{
Bar x;
};
public:
Foo(int a)
{
if (a==0)
new(&x) Bar(12,'a', 34);
else
new(&x) Bar(13);
}
~Foo()
{
x.~Bar();
}
};
需要明确的是,这应该是最后的手段。编译器生成的复制构造函数、移动构造函数等被定义为已删除,因此如果您希望这些操作可用,您也需要实现它们。
这是一个使用委托构造函数的解决方案:
class Foo
{
Bar b;
Foo( Bar b ): b(std::move(b)) { }
public:
Foo(int a): Foo( a ? Bar(13) : Bar(12, 'a', 34) ) {}
};
在委托期间创建所需的对象,然后调用采用 Bar
的构造函数。如果您想允许任何人从 Bar 构造 Foo ,那么您可以使该构造函数 public.
在内存方面比匿名联合解决方案稍微贵一些,但更不容易出错,并且不会产生额外的 copy/move 或动态分配。
class Foo {
private:
std::experimental::optional<Bar> x;
public:
Foo(int a) {
if (a==0) x.emplace(12,'a', 34); // some (int, char, int) constructor of Bar
else x.emplace(13); // (int) constructor of Bar
}
};
此外,在这种情况下 x
将像指针一样使用:x->some_Bar_function()
和 some_function_taking_a_Bar(*x)
。
我认为你问错了问题,与其试图禁止初始化,不如直接去做,即将你的 ctor 拼写为:
Foo(int a) : x((a==0) ? Bar(12,'a', 34) : Bar(13)) {}
这不会导致任何复制或移动(请参阅 here),并且它是惯用的。