如何检查是否隐式生成了移动构造函数?

How can I check if a move constructor is being generated implicitly?

我有几个 class 想要检查是否正在生成默认移动构造函数。有没有办法检查这个(无论是编译时断言,还是解析生成的目标文件,或其他东西)?


励志例子:

class MyStruct : public ComplicatedBaseClass {
    std::vector<std::string> foo; // possibly huge
    ComplicatedSubObject bar;
};

如果任何基类的任何成员或 Complicated...Object classes 的任何成员无法移动,MyStruct 将不会生成其隐式移动构造函数,并且 may 因此无法优化复制 foo 的工作,尽管 foo 是可移动的。


我想避免:

  1. 繁琐地检查 the conditions for implicit move ctor generation,
  2. 显式和递归地默认所有受影响的 classes 的特殊成员函数、它们的基类和它们的成员——只是为了确保移动构造函数可用。

我已经尝试了以下方法,但它们不起作用:

  1. 显式使用 std::move——如果没有可用的移动构造函数,这将调用复制构造函数。
  2. 使用std::is_move_constructible——当有一个复制构造函数接受const Type&时,这将成功,这是默认生成的()。
  3. 使用nm -C检查移动构造函数是否存在(见下文)。然而,另一种方法是可行的(参见答案)。

我试过查看生成的符号 class 像这样:

#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&)

看来这种方法也行不通。


然而,如果目标 class 有一个带有显式移动构造函数的成员,隐式生成的移动构造函数将对目标 class 可见。 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 的移动构造函数的符号,但不是复制构造函数,因为它看起来是完全隐式的。我假设编译器会生成一个平凡的内联移动构造函数,如果可以的话,如果它必须调用其他非平凡的移动构造函数,则会生成一个非平凡的移动构造函数。不过,这仍然无法帮助我完成任务。

  1. 禁用内联(-fno-inline
  2. 要么
    • 确保代码可以使用移动构造函数,或者(更好)
    • 在编译代码的任意位置临时添加对std::move(MyStruct)的调用,以满足odr-used requirement
  3. 要么
    • 确保 MyStruct 至少有一个父 class 或一个非静态成员(递归),带有一个非平凡的移动构造函数(例如 std::string 就足够了), 或 (更简单)
    • 暂时将 std::string 成员添加到您的 class
  4. compile/link 和 运行 通过 nm -C ... | grep 'MyStruct.*&&'
  5. 生成的目标文件

结果将暗示移动构造函数是否生成。


正如问题本身所讨论的那样,这种方法似乎并不能可靠地工作,但是在解决了使其不可靠的两个问题:内联和 triviality of the move constructor 之后,它证明是一种可行的方法。

无论生成的移动构造函数是隐式还是显式默认都不起作用——默认值是否是 trivial or not 是相关的:一个普通的移动(和复制)构造函数将简单地执行对象的字节复制.

正如 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

限制;它还允许 trivial/nothrow 复制构造。

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
};

然后在 class 定义之外显式默认它们:

inline MyStruct::MyStruct(MyStruct&&) noexcept = default;
inline MyStruct& MyStruct::operator=(MyStruct&&) = default;

如果默认函数被隐式定义为已删除,这将触发编译时错误。