【发布时间】:2016-02-29 14:40:12
【问题描述】:
我有几个类,我希望检查是否正在生成默认移动构造函数。有没有办法检查这一点(无论是编译时断言,还是解析生成的目标文件,或其他)?
励志例子:
class MyStruct : public ComplicatedBaseClass {
std::vector<std::string> foo; // possibly huge
ComplicatedSubObject bar;
};
如果任何基类的任何成员或Complicated...Object 类的任何成员都无法移动,MyStruct 将不会生成其隐式移动构造函数,可能因此无法优化掉复制foo,当可以移动时,即使foo 是可移动的。
我希望避免:
- 繁琐检查the conditions for implicit move ctor generation,
- 显式和递归地默认所有受影响类的特殊成员函数、它们的基类和它们的成员 - 只是为了确保移动构造函数可用。
我已经尝试了以下方法,但它们不起作用:
- 显式使用
std::move— 如果没有可用的移动构造函数,这将调用复制构造函数。 - 使用
std::is_move_constructible——当有一个接受const Type&的复制构造函数时,这将成功,这是默认生成的(as long as the move constructor is not explicitly deleted, at least)。 - 使用
nm -C检查是否存在移动构造函数(见下文)。但是,另一种方法是可行的(见答案)。
我尝试查看生成的普通类的符号,如下所示:
#include <utility>
struct MyStruct {
MyStruct(int x) : x(x) {}
//MyStruct(const MyStruct& rhs) : x(rhs.x) {}
//MyStruct(MyStruct&& rhs) : x(rhs.x) {}
int x;
};
int main() {
MyStruct s1(4);
MyStruct s2(s1);
MyStruct s3(std::move(s1));
return s1.x + s2.x + s3.x; // Make sure nothing is optimized away
}
生成的符号如下所示:
$ CXXFLAGS="-std=gnu++11 -O0" make -B x; ./x; echo $?; nm -C x | grep MyStruct | cut -d' ' -f3,4,5
g++ -std=gnu++11 -O0 x.cc -o x
12
.pdata$_ZN8MyStructC1Ei
.pdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
.text$_ZN8MyStructC1Ei
.text$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
.xdata$_ZN8MyStructC1Ei
.xdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
MyStruct::MyStruct(int)
std::remove_reference<MyStruct&>::type&&
当我明确默认复制和移动构造函数(无符号)时,输出是相同的。
使用我自己的复制和移动构造函数,输出如下所示:
$ vim x.cc; CXXFLAGS="-std=gnu++11 -O0" make -B x; ./x; echo $?; nm -C x | grep MyStruct | cut -d' ' -f3,4,5
g++ -std=gnu++11 -O0 x.cc -o x
12
.pdata$_ZN8MyStructC1Ei
.pdata$_ZN8MyStructC1EOKS_
.pdata$_ZN8MyStructC1ERKS_
.pdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
.text$_ZN8MyStructC1Ei
.text$_ZN8MyStructC1EOKS_
.text$_ZN8MyStructC1ERKS_
.text$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
.xdata$_ZN8MyStructC1Ei
.xdata$_ZN8MyStructC1EOKS_
.xdata$_ZN8MyStructC1ERKS_
.xdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
MyStruct::MyStruct(int)
MyStruct::MyStruct(MyStruct&&)
MyStruct::MyStruct(MyStruct const&)
std::remove_reference<MyStruct&>::type&& std::move<MyStruct&>(MyStruct&)
所以看来这种方法也行不通。
但是,如果目标类有一个具有显式移动构造函数的成员,则隐式生成的移动构造函数将对目标类可见。 IE。使用此代码:
#include <utility>
struct Foobar {
Foobar() = default;
Foobar(const Foobar&) = default;
Foobar(Foobar&&) {}
};
struct MyStruct {
MyStruct(int x) : x(x) {}
int x;
Foobar f;
};
int main() {
MyStruct s1(4);
MyStruct s2(s1);
MyStruct s3(std::move(s1));
return s1.x + s2.x + s3.x; // Make sure nothing is optimized away
}
我将得到MyStruct 的移动构造函数的符号,但不是复制构造函数,因为它似乎是完全隐式的。我假设编译器会生成一个普通的内联移动构造函数,如果它必须调用其他非普通的移动构造函数,则会生成一个非普通的移动构造函数。不过,这仍然无法帮助我完成任务。
【问题讨论】:
-
所以在上面的代码中,所有对copy ctor的使用都被省略了;它的存在被检查,但它没有被调用。 (如果您不认识,省略是 C++ 标准中的一个技术术语)。相比之下,实际调用了 move ctor。要强制调用复制 ctor,请写
template<class T> T const& copy(T const& t){return t;},然后写MyStruct s2(copy(s1));。然后复制 ctor 可能会出现在您的转储中? -
重要的往往不是移动 ctor,而是移动操作是不抛出的。无法有效移动包含 1000 个原始字节数组的结构;定义移动 ctor 没有什么意义。复制 ctor 也可以完成这项工作。需要分配的结构通常会从移动中受益(因为您可以删除分配);在这种情况下,复制 ctor 可以抛出(分配失败),而移动 ctor 不会(因为它只是从传入的对象中窃取数据)。也许是那种方法?
-
@Yakk:我挑战你证明复制构造函数被省略 :-) 任何省略的基本前提是源对象和目标对象可以被视为一个和相同的。就像从函数返回本地对象或临时对象一样——如果我们首先在目标对象中构造该对象,则不会丢失任何内容。在这里,所有对象都是独立的,不能进行省略。
-
OTOH,您的第二条评论使我使用了具有显式复制和移动构造函数的 std::string。事实上,隐式生成的移动构造函数出现在
MyStruct。我猜这与构造函数是否微不足道有关。请注意,任何优化级别根本不会导致构造函数符号 - 可能是由于内联。等等,-fno-inline? -
也许让你的复制构造函数抛出,然后使用
is_nothrow_move_constructible?
标签: c++ c++11 move-constructor