从用户定义的转换直接初始化可优化中间对象

问题描述 投票:0回答:1

我正在寻找一种方法来就地初始化元组,并找到了这个发布的解决方案,它依赖于类之间的转换。

我想知道应用哪些规则会导致中间对象“省略”。

我构建了这个最小的示例来说明可能的问题。

#include <cstdio>
#include <tuple>

class To {
   public:
    To() { std::puts("default constructor"); }
    explicit To(int input) : val(input) { std::puts("value constructor"); }
    // noexcept is a sweet lie because of std::puts
    To(To&& object) noexcept : val(object.val) {
        std::puts("move constructor");
    }
    // noexcept is a sweet lie because of std::puts
    To& operator=(To&& object) noexcept {
        val = object.val;
        std::puts("move assign");
        return *this;
    }
    ~To() noexcept { std::puts("destructor"); }

   private:
    int val = -1;
};

class From {
   public:
    explicit From(int input) : val(input) {
        std::puts("From value constructor");
    }
    // marking explicit to inhibit to1 assignement
    operator To() const&& { return To{val}; };

   private:
    int val = -1;
};

To Make_To(int val) { return To{val}; }

int main() {
    std::puts("-----------------");
    std::puts("# direct init with conversion");
    const std::tuple<To> tp0{From{0}};
    std::puts("# direct init with creator");
    const std::tuple<To> tp1{Make_To(1)};
    std::puts("# init and assign");
    std::tuple<To> tp2;
    tp2 = From{2};
    std::puts("# finish");
}

实时 C++17 (我使用 C++17 来保证 RVO)。

输出为:

-----------------
# direct init with conversion
From value constructor
value constructor
# direct init with creator
value constructor
move constructor
destructor
# init and assign
default constructor
From value constructor
value constructor
move assign
destructor
# finish
destructor
destructor
destructor

我让创建和分配作为 RVO 无法完全应用为目标对象的情况的参考,因为它已经开始了其生命周期。

为什么转换的初始化和创建者的初始化有区别?

c++ type-conversion stdtuple copy-elision return-value-optimization
1个回答
0
投票

没有任何规则规定:正式地,使用类型为

To
的参数直接初始化
From
会选择移动构造函数,并使用通过调用
From::operator To()
生成的临时值来调用移动构造函数。

这是CWG2327,有些人试图通过添加额外的初始化规则来解决这个问题,您可以在这里看到:P2828

编译器已经实现了预期的行为,其中

To(from_object)
的工作方式与
To(from_object.operator To())
完全相同。


至于您所看到的差异,

const std::tuple<To> tp1{Make_To(1)};
调用带有
To&&
的元组构造函数模板,因此必须有一个临时对象来绑定到右值引用。然后使用该右值引用来初始化元组的元素,调用移动构造函数。

const std::tuple<To> tp0{From{0}};
调用带有
From&&
的构造函数。元组的元素是从此右值引用初始化的,它不会调用移动构造函数(
operator To()
的目标是元组的成员,因此没有任何可移动的内容,这是非标准的,但这就是编译器的内容做)。

© www.soinside.com 2019 - 2024. All rights reserved.