隐式移动构造函数何时不够好?

28

隐式移动构造函数何时不够用?

我是否应该像析构函数和复制构造函数一样处理它,只有在我自己管理内存时才通常需要使用它?

在这种(非常牵强的)情况下,隐式移动构造函数是否足够好:

class A
{
private:
    B b;
    std::string name;

public:
    A();
    std::string getName() const {
        return name;
    }

    B getB() const {
        return b;
    }
};

class B
{
private:
    std::vector list;

public: 
    B();
    std::vector getList() const {
        return list;
    }
};

1
有时候你需要手动编写移动构造函数来保持成员之间的“对齐”。如果你有一个字符串成员和一个指向字符串特定部分的 int 成员,那么当对象被移动时,int 成员应该重置为 0 - Mooing Duck
2
类型为 A 的对象的隐式移动构造函数将调用 nameb 的移动构造函数(它们也是隐式的,并调用 list 的移动构造函数)。一切都已经正确移动,无需进行任何操作。如果 A 具有自己的堆管理或不可移动成员(在这种情况下,您可能需要单独处理这些成员对象),则需要编写自己的移动构造函数。 - Pixelchemist
1
“当隐式移动构造函数不够好时?” - 当它们甚至没有被一个主要编译器供应商的愚蠢生成时。 ;) (抱歉发牢骚,但这绝对是VC ++中最缺乏的功能。) - Christian Rau
2个回答

24

这里的答案基于Google搜索结果。

引用自Andrzej's C++ blog

> 什么时候应该为我的类定义移动构造函数?

这主要取决于你的类做了什么以及它是如何实现的。首先,对于“聚合”类(仅为方便/清晰而分组其他数据的类),编译器将隐式生成移动构造函数。考虑以下类。

struct Country {
  std::string name;
  std::vector<std::string>  cities;
};

在一个典型的C++结构体中,许多特殊成员函数(如复制构造函数、复制赋值、析构函数)都是自动生成的。这也包括移动构造函数(和移动赋值)。

对于更复杂的类,封装其实现细节,答案就更有趣了。移动语义(移动构造函数、移动赋值)的主要目标之一是为编译器提供两个工具,实现用户定义类型的值语义(按值传递参数,按值返回):

  1. 从一个对象创建两个相同的对象——这需要耗费大量时间。
  2. 将一个对象从一个内存位置移至另一个位置——可以使其非常快速。

如果对于您的类,可以实现比复制构造函数更快的移动构造函数,则应该出于运行时速度优化目的实现它。 我们已经看到了如何在向量中实现它,在此链接中。然而,并非所有类型都能实现这样的移动构造函数,这比复制构造函数更快。考虑以下矩阵表示方式。

class Matrix {
  std::complex<long double> data[1000][1000];
};

因为矩阵表示所需的所有内存都在类范围内声明(不像vector使用堆分配的内存),所以没有办法仅应用少量赋值。我们需要为每个数组元素进行复制。定义移动构造函数是没有意义的,因为它不会比复制更快。

如果您希望启用无法复制的类型(因为它类似于RAII并表示资源)仍能按值传递而不需要复制,并存储在STL容器中,则提供移动构造函数是另一种有效的原因。这种独特的所有权语义在此链接中有更详细的说明。


谢谢,这正是我在寻找的答案。 - DormoTheNord
3
@DormoTheNord 欢迎你。应该要归功于Andrzej。我也从你的问题中学到了东西。 - taocp

16
必要的零规则答案:设计管理单个资源的类,这样就可以重写移动/复制/析构/赋值,或者设计聚合资源管理器并且不需要重写。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接