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

时间:2015-11-26 13:14:39

标签: c++ c++11 move-constructor

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

励志示例:

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

如果任何基类或Complicated...Object个类的成员中的任何成员无法移动,MyStruct将不会生成其隐式移动构造函数,并且可能因此无法优化即使foo可以移动,也可以在完成移动时复制foo

我希望避免:

  1. 繁琐地检查the conditions for implicit move ctor generation
  2. 显式地和递归地默认所有受影响的类,它们的基础和它们的成员的特殊成员函数 - 只是为了确保移动构造函数可用。
  3. 我已经尝试过以下操作但它们不起作用:

    1. 明确使用std::move - 如果没有移动ctor,这将调用副本ctor。
    2. 使用std::is_move_constructible - 如果复制构造函数接受const Type&,默认情况下生成as long as the move constructor is not explicitly deleted, at least),这将成功。
    3. 使用nm -C检查移动ctor的存在[见下文]。但是,另一种方法是可行的[见答案]。
    4. 我试着查看像这样的普通类的生成符号:

      #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。这仍然无法帮助我完成任务。

3 个答案:

答案 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)

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

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

    正如问题本身所讨论的那样,这种方法似乎无法可靠地运行,但在解决了导致其不可靠的两个问题后:内联和triviality of the move constructor,结果证明这是一种有效的方法。

    生成的移动构造函数是隐式还是显式默认不起作用 - 默认值是trivial or not是否相关:一个简单的移动(和复制)构造函数将只执行对象的逐字节复制。 / p>