【问题标题】:Move assignment in overloaded vector summation重载向量求和中的移动赋值
【发布时间】:2020-06-17 22:04:26
【问题描述】:

我正在阅读 C++ 之旅(第 5.2 节复制和移动)。按照书中的说明,我构建了一个名为Vector 的容器(它模仿了std::vector)。我的目标是有效地实现以下(逐元素)求和:

Vector r = x + y + z;

根据本书,如果我没有移动赋值和构造函数,+ 运算符最终会不必要地复制Vectors。所以我实现了移动赋值和构造函数,但我认为当我运行Vector r = x + y + z; 时编译器仍然没有使用它们。我错过了什么?我很感激任何反馈。下面是我的代码。我希望看到输出Move assignment,但我没有得到任何输出。 (求和部分有效,只是搬家业务我不确定)

代码

// Vector.h
class Vector{

public:
    explicit Vector(int);
    Vector(std::initializer_list<double>);
    // copy constructor
    Vector(const Vector&);
    // copy assignment
    Vector& operator=(const Vector&);
    // move constructor
    Vector(Vector&&);
    // move assignment
    Vector& operator=(Vector&&);
    ~Vector(){delete[] elem;}
    double& operator[](int) const;
    int size() const;
    void show();
    friend std::ostream& operator<< (std::ostream& out, const Vector& vec);

private:
    int sz;
    double* elem;
};

Vector operator+(const Vector&,const Vector&);

// Vector.cpp

Vector::Vector(std::initializer_list<double> nums) {
    sz = nums.size();
    elem = new double[sz];
    std::initializer_list<double>::iterator it;
    int i = 0;
    for (it=nums.begin(); it!=nums.end(); ++it){
        elem[i] = *it;
        ++i;
    }
}

Vector::Vector(Vector&& vec) {
    sz = vec.sz;
    vec.sz = 0;
    elem = vec.elem;
    vec.elem = nullptr;
    std::cout<<"Move constructor"<<std::endl;
}

Vector& Vector::operator=(Vector&& vec) {
    if (this == &vec){
        return *this;
    }
    sz = vec.sz;
    vec.sz = 0;
    elem = vec.elem;
    vec.elem = nullptr;
    std::cout<<"Move assignment"<<std::endl;
    return *this;

Vector operator+(const Vector& vec1, const Vector& vec2){
    if (vec1.size() != vec2.size()){
        throw std::length_error("Input vectors should be of the same size");
    }
    Vector result(vec1.size());
    for (int i=0; i!=vec1.size(); ++i){
        result[i] = vec1[i]+vec2[i];
    }
    return result;
}
}
// Main
int main() {
    Vector x{1,1,1,1,1};
    Vector y{2,2,2,2,2};
    Vector z{3,3,3,3,3};
    Vector r = x + y + z;
} // Here I expect the output: Move assignment, but I get no output.

【问题讨论】:

  • 您希望行的哪一部分执行移动assignment?您需要创建临时对象,因为 operator+Vector r 是从该临时对象构造的
  • 您的移动赋值运算符泄漏内存。
  • @PaulMcKenzie 我看不到泄漏,你能解释一下吗?
  • elem = vec.elem; -- “老”元素怎么了?好像你没有delete []它。
  • 一旦执行该行,您就丢失了elem 指向的原始地址。你怎么去delete[]那段记忆?你应该做的是std::swapelem 值,这样传入的对象就可以销毁旧数据。对于移动分配,您希望交换数据而不是直接用nullptr 替换它(如移动构造函数)。

标签: c++ class vector move-constructor copy-elision


【解决方案1】:

发生移动省略。

根据 C++ 17 标准(12.8 复制和移动类对象)

31 当满足某些条件时,允许省略实现 类对象的复制/移动构造,即使构造函数 选择用于复制/移动操作和/或析构函数 对象有副作用。 在这种情况下,实现将 省略的复制/移动操作的源和目标只是两个 引用同一对象的不同方式,以及 该对象发生在两个对象的较晚时间 没有优化就会被破坏。 122 复制/移动操作,称为复制省略,在 以下情况(可以结合起来消除多个 副本):

(31.3) — 当临时类对象尚未绑定到 参考(12.2)将被复制/移动到具有相同的类对象 cv-unqualified 类型,复制/移动操作可以省略 将临时对象直接构造到 省略复制/移动

因此省略了移动构造函数。由表达式x + y + z; 创建的第二个临时对象直接构建为对象r。

Vector r = x + y + z;

还要考虑到操作符的return语句中有移动省略

(31.1) — 在具有类返回类型的函数的返回语句中, 当表达式是非易失性自动对象的名称时 (除了函数或 catch 子句参数)具有相同的 cvunqualified 类型作为函数返回类型,复制/移动 直接构造自动对象可以省略操作 进入函数的返回值

因此,在表达式x + y(在操作符内)中创建的临时对象将在表达式( x + y ) + z 中被移动(即相对于返回语句的省略 - 前面的引号),它将被使用常量左值引用和在这个表达式中创建的新临时对象将被构建为 r。

整个代码sn-p

Vector x{1,1,1,1,1};
Vector y{2,2,2,2,2};
Vector z{3,3,3,3,3};
Vector r = x + y + z;

由于移动省略,将创建 5 个 Vector 类的对象。

【讨论】:

  • 外层operator+的结果对象与r相同,本文不涉及,因为C++17(不再是复制省略场景,结果对象现在是@ 987654328@ 而不是临时的)。从operator+ 实现中的变量到结果对象有NRVO,这是与您引用的不同的子句(class.copy.elision/3)
  • 这也是 15.8.3/1,而不是 12.8/31
  • @M.M 不,报价是正确的。可以应用两个移动/复制省略。我在 return 语句中提供了对象 r 的引用而不是省略。
  • 那句话不适用于r。不涉及“临时类对象”,operator+ 的结果对象是r
  • @M.M 这里结合了两种省略。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-18
  • 1970-01-01
  • 1970-01-01
  • 2013-04-10
  • 2021-07-05
  • 1970-01-01
相关资源
最近更新 更多