如何避免在c ++ 11中触发这种复制构造函数?

时间:2019-04-12 12:58:33

标签: c++ c++11 c++17

我想在函数中创建对象并在外部使用它。

我按照c ++ 17标准编写了以下代码,看来还可以。

#include <iostream>

struct Vector2 {
    Vector2() = default;
    Vector2(int x, int y) : x(x), y(y) {}
    Vector2(const Vector2 &) = delete;
    Vector2 &operator=(const Vector2 &) = delete;

    int x = 0;
    int y = 0;
};

Vector2 newVec(int x, int y) {
    return Vector2(x, y);
}

int main() {
    auto v = newVec(1, 2);
    std::cout << v.x * v.y << std::endl;
    return 0;
}

但是当我切换到c ++ 11标准时,我无法编译它。

  

注意:“ Vector2”已在此处明确标记为已删除       Vector2(const Vector2&)= delete;

我认为我在newVec函数中构造了一个临时Vector2对象。返回时,Vector2使用临时变量作为参数调用副本构造函数。但是我已将复制构造函数标记为“删除”,因此无法编译。

我的问题是C ++ 11标准下的等效代码是什么? c ++ 17为避免此副本构造函数做了什么?

4 个答案:

答案 0 :(得分:6)

  

c ++ 17为避免此副本构造函数做了什么?

此:

auto v = newVec(1, 2);

是一种被称为“保证复制省略”的语言功能的动机之一。 that paper中的示例:

auto x = make(); // error, can't perform the move you didn't want,
                 // even though compiler would not actually call it

在C ++ 17之前,该表达式始终进行复制初始化。我们用auto推导类型,得到Vector2,然后尝试从类型Vector2的右值构造Vector2。这是通常的过程-枚举构造函数等。最匹配的是您的副本构造函数,该副本构造函数被删除,因此整个内容格式不正确。

悲伤的脸。

在C ++ 17中,这完全不同。从相同类型的prvalue初始化根本不会尝试找到构造函数。没有副本或移动。它只是给您一个Vector2,即newVec(1, 2)directly的值。这就是这里的更改-复制初始化不是在C ++ 17中起作用,更多的是它甚至不再是同一种初始化。


  

我的问题是C ++ 11标准下的等效代码是什么?

根本没有办法构造这样的不可移动对象。在同一篇论文中:

struct NonMoveable { /* ... */ };
NonMoveable make() { /* how to make this work without a copy? */ }

如果您希望像make()(在示例中为newVec())之类的函数起作用,则该类型必须至少是可移动的。这意味着:

  • 添加移动构造器
  • 取消删除副本构造函数
  • 如果以上都不是,请将其放在堆上-用unique_ptr<Vector2>之类的可移动的东西包装

或者您将make()传递给对象并让函数在内部填充它:

void make(NonMoveable&);

这最终变得难以组合,但是可以工作。按照书面规定,它要求您的类型默认为可构造的,但也有一些解决方法。

答案 1 :(得分:3)

您在C ++ 17中观察guaranteed copy elision

另一方面,即使编译器最终决定退出其调用,C ++ 11也要求存在一个副本或移动构造函数并且可以访问该构造函数。

答案 2 :(得分:2)

您需要删除对复制构造函数的显式删除,以便生成move构造函数(但也会是copy构造函数),或者像这样声明一个move构造函数:

Vector2(Vector2 &&) = default;

根据标准

  

如果类X的定义未明确声明移动   构造函数,当且仅当一个构造函数被隐式声明为默认构造函数   如果

     

— X没有用户声明的副本构造函数,

     

— X没有用户声明的副本分配运算符,

     

— X没有用户声明的移动分配运算符,并且

     

— X没有用户声明的析构函数。

用户声明是指用户提供(由用户定义),显式默认(=默认)或显式删除(=删除)

另一方面,在C ++ 17中,由于描述here的原因,省略了对复制/移动构造函数的调用:

  

在以下情况下,允许编译器,但即使复制/移动(自C ++ 11起)构造函数和析构函数也不需要省略类对象的复制和移动(自C ++ 11起)构造有明显的副作用。这些对象直接构造到存储中,否则会将它们复制/移动到其中。

     

...

     

(从C ++ 17开始)在对象的初始化中,当源对象是一个无名的临时对象,并且与目标对象具有相同的类类型(忽略cv限定)时。当无名临时变量是return语句的操作数时,复制省略的这种变体称为RVO,即“返回值优化”。   (直到C ++ 17)

     

返回值优化是强制性的,不再视为复制省略;见上文。

答案 3 :(得分:1)

您可以通过两个小的更改来修复它:

    来自return {x, y};
  1. newVec()。这样只会构造对象一次,不需要复制构造函数。
  2. const auto& v而不是auto v。这将具有相同的生存期(但您不能对其进行修改),并且不需要复制构造函数。