【问题标题】:When are implicit move constructors not good enough?什么时候隐式移动构造函数不够好?
【发布时间】:2013-06-10 12:11:24
【问题描述】:

什么时候隐式移动构造函数不够好?

我是否应该将它视为析构函数和复制构造函数,通常只有在我管理自己的内存时才需要它?

在这个(非常做作的)场景中,隐式移动构造函数是否足够好:

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

【问题讨论】:

  • 有时您需要手动编写移动构造函数来保持成员之间的“对齐”。如果您有一个字符串成员和一个引用字符串特定部分的int 成员,那么当移动对象时,int 成员可能应该重置为0
  • A 类型对象的隐式移动构造函数将调用nameb 的移动构造函数(这也是隐式的,并调用list 的移动构造函数)。一切正常,与你无关。如果A 有自己的堆管理或不可移动的成员(在这种情况下,您可能希望单独处理这些成员对象),请编写您自己的。
  • “什么时候隐式移动构造函数不够好?” - 当它们甚至不是由主要编译器供应商的愚蠢生成时。 ;) (抱歉,这绝对是 VC++ 中最严重缺失的功能。)

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


【解决方案1】:

这里的答案是基于谷歌搜索的结果。

引用Andrzej's C++ blog

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

这很大程度上取决于你的类做什么以及它是如何实现的。首先,对于“聚合”类,为了方便/清晰而只对其他数据进行分组,移动构造函数将由编译器隐式生成。考虑下面的类。

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

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

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

  1. 将两个相同的对象从一个对象中提取出来——成本很高。
  2. 将一个对象从一个内存位置移动到另一个位置 — 可以 速度很快。

如果你的类可以实现比复制构造函数更快的移动构造函数,你应该实现它以优化运行时速度。我们已经看到了它是如何实现的用于this link 中的矢量。但是,并非所有类型都可以实现这种移动构造函数,比复制构造函数更快。考虑以下矩阵表示。

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

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

另一个提供移动构造函数的正当理由是,如果你想让你的类型不可复制(因为它类似于 RAII 并且代表资源)在不需要复制的情况下仍然按值传递,并存储在 STL 容器中。this link 中更详细地解释了这种独特的所有权语义。

【讨论】:

  • 谢谢,正是我想要的答案。
  • @DormoTheNord 欢迎您。应该归功于 Andrzej。我也从你的问题中学到了。
【解决方案2】:

必选Rule of Zero 答案:设计管理单个资源的类 - 从而覆盖移动/复制/析构函数/分配 - 或聚合资源管理器且不需要覆盖的类。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-12
    • 2020-09-07
    • 1970-01-01
    • 2018-10-05
    • 2013-01-22
    • 2019-10-16
    相关资源
    最近更新 更多