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

How to avoid triggering this kind of copy constructor in c++11?

本文关键字:复制 构造函数 何避免 c++11      更新时间:2023-10-16

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

我在 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 做了什么来避免这个复制构造函数?

c++17 做了什么来避免这个复制构造函数?

这:

auto v = newVec(1, 2);

是名为"保证复制消除"的语言功能的激励案例之一。该论文中的示例:

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)的价值,直接。这就是这里的变化 - 并不是说在 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&);

这最终会降低作曲能力,但它有效。如前所述,它要求你的类型是默认的可构造的,但也有一些解决方法。

您在 C++17 中观察到保证副本省略。

另一方面,C++11 要求复制或移动构造函数存在且可访问,即使编译器最终决定避免其调用也是如此。

您需要删除复制构造函数的显式删除,以便生成移动构造函数(但复制构造函数也是如此),或者像这样声明移动构造函数:

Vector2(Vector2 &&) = default;

根据标准,

如果类 X 的定义没有显式声明移动 构造函数,一个将被隐式声明为默认值,当且仅 如果

— X 没有用户声明的复制构造函数,

— X 没有用户声明的复制赋值运算符,

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

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

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

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

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

(自 C++17) 在对象的初始化中,当源对象是无名临时对象并且与目标对象属于相同的类类型(忽略 cv-quality)时。当无名临时是 return 语句的操作数时,这种复制省略的变体称为 RVO,即"返回值优化"。 (至C++17)

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

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

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