如何检查是否正在隐式生成移动构造函数
How can I check if a move constructor is being generated implicitly?
>我有几个类,我想检查是否正在生成默认的移动构造函数。有没有办法检查这一点(无论是编译时断言,还是解析生成的对象文件,或者其他东西(?
励志例子:
class MyStruct : public ComplicatedBaseClass {
std::vector<std::string> foo; // possibly huge
ComplicatedSubObject bar;
};
如果任何基的任何成员或Complicated...Object
类的任何成员无法移动,则MyStruct
将不会生成其隐式移动构造函数,因此可能无法优化复制foo
的工作,当可以完成移动时,即使foo
是可移动的。
我希望避免:
- 繁琐地检查隐式移动 CTOR 生成的条件,
- 显式和递归地默认所有受影响的类、其基和成员的特殊成员函数,只是为了确保 Move 构造函数可用。
我已经尝试了以下方法,但它们不起作用:
- 显式使用
std::move
- 如果没有可用的移动构造函数,这将调用复制构造函数。 - use
std::is_move_constructible
—当有复制构造函数接受默认生成的const Type&
时,此操作将成功(至少只要未显式删除移动构造函数(。 - 使用
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&)
因此,这种方法似乎也行不通。
但是,如果目标类
具有具有显式移动构造函数的成员,则隐式生成的移动构造函数将对目标类可见。 即使用此代码:
#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
的移动构造函数的符号,但不会得到复制构造函数,因为它似乎是完全隐式的。我假设编译器会生成一个普通的内联移动构造函数(如果可以(,如果它必须调用其他非平凡的移动构造函数,则生成一个非平凡的移动构造函数。不过,这仍然对我的追求没有帮助。
声明您希望存在于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;
如果默认函数被隐式定义为已删除,这将触发编译时错误。
正如 Yakk 指出的那样,它是否是编译器生成的通常无关紧要。
您可以检查类型是否简单或可构造 nothrow 移动
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
限制;它还允许琐碎/无抛掷复制构造。
- 禁用内联 (
-fno-inline
( - 也
- 确保代码可以使用移动构造函数,或者(更好(
- 临时添加对已编译代码中任意位置
std::move(MyStruct)
的调用,以满足 ODR 使用的要求
- 也
- 确保
MyStruct
至少有一个父类或非静态成员(递归(,具有非平凡的移动构造函数(例如,std::string
就足够了(,或者(更容易( - 临时将 std::string 成员添加到类中
- 确保
- 通过
nm -C ... | grep 'MyStruct.*&&'
编译/链接并运行生成的对象文件
结果将暗示是否生成了移动构造函数。
正如问题本身所讨论的,这种方法似乎并不可靠,但在修复了使其不可靠的两个问题:内联和移动构造函数的琐碎性之后,它被证明是一种工作方法。
生成的移动构造函数是隐式还是显式默认不重要 - 默认值是否平凡是相关的:平凡移动(和复制(构造函数将只执行对象的字节复制。
- 为什么不调用移动构造函数?(默认情况下只有构造器,没有别的)
- std::vector::p ush_back() 不会在 MSVC 上编译具有已删除移动构造函数的对象
- 仅包含可移动 std::map 的类的移动构造函数不起作用
- 为什么调用复制构造函数而不是移动构造函数?
- 基类中的默认析构函数禁用子类中的移动构造函数(如果有成员)
- 从具有按值捕获的 lambda 移动构造 std::函数时,移动构造函数调用两次
- 具有已删除移动和复制构造函数的类的就地构造
- 移动构造函数和右值引用
- 使用移动调用对等构造函数unique_ptr默认构造函数
- 为什么 std::memmove 中联合的默认非平凡移动构造函数C++?
- 具有专用化的模板类中的可靠条件复制和移动构造函数
- 构造函数采用std::string_view与std::string并移动
- C++:为什么不调用移动构造函数?
- 了解构造函数在移动、复制、赋值语义中的行为
- 没有移动的构造函数移动课程
- 引用绑定和复制构造函数/移动构造函数
- 构造函数移动
- C++ 向量实现 - 移动构造函数 - 移动与前进
- 我真的必须取消移动构造函数/移动结构中的所有成员还是只是指针
- 将类(没有默认构造函数)移动到另一个类的move构造函数中