当返回一个用带括号的init列表初始化的对象时,我保证有一对构造函数和析构函数调用吗?

Am I guaranteed a single pair of constructor and destructor calls when returning an object initialized with braced init list?

本文关键字:构造函数 函数调用 析构 初始化 一个 返回 列表 init 对象      更新时间:2023-10-16

以以下类为例

#include <iostream>
using namespace std;
class A{
private:
    int a_;
    int b_;
    A(const A&) = delete;
    A& operator=(const A&) = delete;
    A(A&&) = delete;
    A& operator=(A&&) = delete;
public:
    A(int a, int b) : a_{a}, b_{b}{cout<<"constructedn";}
    void ephemeral() const{cout<<"operationn";}
    ~A(){cout<<"destructedn";}
};
A make_A(int a, int b){
    return {a, b};
}
int main(){
    make_A(1, 2).ephemeral();
    return 0;
}

给出了预期的结果。

构造对象,执行操作,然后对其进行析构。

然而,我担心这是否得到保证。我主要担心的是,由于标准给予编译器的自由,我是否会看到任何我没有意识到的影响。

我不认为copy-省略是这里的一个因素,因为所有的move和copy构造函数都被声明为已删除,所以它们怎么可能被调用呢?

唯一被调用的构造函数是接受两个整数的构造函数。我能确定这将在编译器、平台和优化级别上保持一致吗?

我怀疑答案是肯定的,但可能有微妙之处。

return {a,b};时直接构造返回值

没有创建临时的、逻辑的或其他的。没有遗漏。

该返回值在main的返回上下文中可用。你可以称之为.ephemeral()操作。在完整表达式的末尾,它将超出作用域,除非您将其"存储"在A const&(引用生命周期扩展启动)或A&&(同上)或auto const&auto&&变量中,如下所示:

auto&& a = make_A(1, 2);
a.ephemeral();

在上面的例子中,没有复制发生。

这些操作都不会导致不符合标准的副本。

你是正确的,在某些情况下,复制结构可以被省略掉。省略是指两个对象的标识和生存期合并。所以如果make_A read:

A make_A(int a, int b){
  A r{a,b};
  return r;
}

r可以在返回值中省略。然而,在这里,编译器会要求定义A(A const&)A(A&&),因此它不会与A一起编译。在实践中,一旦检查了它们的定义,它就不会调用它们,因为make_A中的r将被省略为与make_A的返回值相同的对象。

相似的,

A a = make_A(1,2);
省略make_A返回的临时变量

,使其与命名变量a相同。省略是暂时的,因此也可以在 make_A中同时省略变量。在本例中,还需要存在A(A&&)A(A const&)

通过删除move/copy函数,它们不能被调用,因此对象不能被复制。每个构造函数只能调用一个析构函数(除非手动构造或析构)。

如果代码试图调用它们,它将在编译时生成一个错误。


在c++ 17中,你甚至可以使用return A(a,b);和类似的保证。

你也可以A a = make_A(1,2);和类似的保证发生。

这被描述为"保证省略",而是将一些操作转换为"如何构造某些东西的描述"。

所以在某些情况下,你可以在c++ 03或c++ 11或c++ 14中做需要移动或复制因子的事情,但在c++ 17中现在可以做类似于"省略"的事情,不再需要移动或复制因子