我有几个类,我想检查是否正在生成默认的移动构造函数。有没有办法检查这个(无论是编译时断言,还是解析生成的目标文件,还是别的东西)?
励志示例:
class MyStruct : public ComplicatedBaseClass {
std::vector<std::string> foo; // possibly huge
ComplicatedSubObject bar;
};
如果任何基类或Complicated...Object
个类的成员中的任何成员无法移动,MyStruct
将不会生成其隐式移动构造函数,并且可能因此无法优化即使foo
可以移动,也可以在完成移动时复制foo
。
我希望避免:
我已经尝试过以下操作但它们不起作用:
std::move
- 如果没有移动ctor,这将调用副本ctor。std::is_move_constructible
- 如果复制构造函数接受const Type&
,默认情况下生成as long as the move constructor is not explicitly deleted, at least),这将成功。nm -C
检查移动ctor的存在[见下文]。但是,另一种方法是可行的[见答案]。我试着查看像这样的普通类的生成符号:
#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&&
当我明确默认复制并移动ctors(无符号)时输出相同。
使用我自己的副本和移动ctors,输出如下:
$ 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&)
所以看来这种方法也行不通。
但是,如果目标类具有带有显式移动构造函数的成员,则隐式生成的移动构造函数将对目标类可见。即使用此代码:
#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
移动ctor的符号,但不是复制符号,因为它似乎是完全隐含的。我认为如果可以的话,编译器会生成一个简单的内联移动ctor,如果它必须调用其他非平凡的移动ctors,则会产生一个非平凡的移动ctor。这仍然无法帮助我完成任务。
答案 0 :(得分:11)
在MyStruct
中声明您想要存在的特殊成员函数,但不要默认要检查的成员函数。假设您关心移动函数,并且还想确保移动构造函数为noexcept
:
struct MyStruct {
MyStruct() = default;
MyStruct(const MyStruct&) = default;
MyStruct(MyStruct&&) noexcept; // no = default; here
MyStruct& operator=(const MyStruct&) = default;
MyStruct& operator=(MyStruct&&); // or here
};
然后在类定义之外显式默认它们:
inline MyStruct::MyStruct(MyStruct&&) noexcept = default;
inline MyStruct& MyStruct::operator=(MyStruct&&) = default;
如果默认函数被隐式定义为已删除,则会触发编译时错误。
答案 1 :(得分:5)
正如Yakk指出的那样,如果编译器生成与否则通常无关紧要。
你可以检查某个类型是否微不足道或者是否可以移动构造
template< class T >
struct is_trivially_move_constructible;
template< class T >
struct is_nothrow_move_constructible;
http://en.cppreference.com/w/cpp/types/is_move_constructible
限制;它还允许琐碎/不复制的复制结构。
答案 2 :(得分:1)
-fno-inline
)std::move(MyStruct)
的调用以符合odr-used requirement MyStruct
至少有一个父类或非静态成员(递归地),使用非平凡的移动构造函数(例如std::string
就足够了),或者(更简单) nm -C ... | grep 'MyStruct.*&&'
结果将暗示是否生成了移动构造函数。
正如问题本身所讨论的那样,这种方法似乎无法可靠地运行,但在解决了导致其不可靠的两个问题后:内联和triviality of the move constructor,结果证明这是一种有效的方法。
生成的移动构造函数是隐式还是显式默认不起作用 - 默认值是trivial or not是否相关:一个简单的移动(和复制)构造函数将只执行对象的逐字节复制。 / p>