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

Irf*_*rfy 17 c++ move-constructor c++11

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


励志示例:

class MyStruct : public ComplicatedBaseClass {
    std::vector<std::string> foo; // possibly huge
    ComplicatedSubObject bar;
};
Run Code Online (Sandbox Code Playgroud)

如果任何类的任何基Complicated...Object类或成员的任何成员都不能被移动,MyStruct则不会生成其隐式移动构造函数,并且因此可能无法优化复制foo工作,即使foo可移动也可以进行移动.


我希望避免:

  1. 繁琐地检查隐式移动ctor生成的条件,
  2. 显式地和递归地默认所有受影响的类,它们的基础和它们的成员的特殊成员函数 - 只是为了确保移动构造函数可用.

我已经尝试过以下内容但它们不起作用:

  1. std::move明确使用- 如果没有可用的移动ctor,这将调用复制ctor.
  2. 使用std::is_move_constructible-this将在复制构造函数接受时成功const Type&,默认情况下生成(只要移动构造函数未被显式删除,至少).
  3. 使用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
}
Run Code Online (Sandbox Code Playgroud)

生成的符号如下所示:

$ 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&&
Run Code Online (Sandbox Code Playgroud)

当我明确默认复制并移动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&)
Run Code Online (Sandbox Code Playgroud)

所以看来这种方法也行不通.


但是,如果目标类具有带有显式移动构造函数的成员,则隐式生成的移动构造函数对于目标类是可见的.即使用此代码:

#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
}
Run Code Online (Sandbox Code Playgroud)

我会得到MyStruct移动ctor 的符号,但不会复制ctor,因为它似乎是完全隐含的.我认为如果可以的话,编译器会生成一个简单的内联移动ctor,如果它必须调用其他非平凡的移动ctors,则会产生一个非平凡的移动ctor.这仍然无法帮助我完成任务.

T.C*_*.C. 12

声明要存在的特殊成员函数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
};
Run Code Online (Sandbox Code Playgroud)

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

inline MyStruct::MyStruct(MyStruct&&) noexcept = default;
inline MyStruct& MyStruct::operator=(MyStruct&&) = default;
Run Code Online (Sandbox Code Playgroud)

如果默认函数被隐式定义为已删除,则会触发编译时错误.


Joh*_*erg 5

正如Yakk指出的那样,如果编译器生成与否则通常无关紧要.

可以检查类型是否是微不足道或不可移动构造

template< class T >
struct is_trivially_move_constructible;

template< class T >
struct is_nothrow_move_constructible;
Run Code Online (Sandbox Code Playgroud)

http://en.cppreference.com/w/cpp/types/is_move_constructible

局限性; 它还允许琐碎/不擅长复制结构.