gpt4 book ai didi

c++ - 完美的转发和构造函数

转载 作者:搜寻专家 更新时间:2023-10-31 00:26:25 25 4
gpt4 key购买 nike

我试图了解完美转发和构造函数的相互作用。我的示例如下:

#include <utility>
#include <iostream>


template<typename A, typename B>
using disable_if_same_or_derived =
std::enable_if_t<
!std::is_base_of<
A,
std::remove_reference_t<B>
>::value
>;


template<class T>
class wrapper {
public:
// perfect forwarding ctor in order not to copy or move if unnecessary
template<
class T0,
class = disable_if_same_or_derived<wrapper,T0> // do not use this instead of the copy ctor
> explicit
wrapper(T0&& x)
: x(std::forward<T0>(x))
{}

private:
T x;
};


class trace {
public:
trace() {}
trace(const trace&) { std::cout << "copy ctor\n"; }
trace& operator=(const trace&) { std::cout << "copy assign\n"; return *this; }
trace(trace&&) { std::cout << "move ctor\n"; }
trace& operator=(trace&&) { std::cout << "move assign\n"; return *this; }
};


int main() {
trace t1;
wrapper<trace> w_1 {t1}; // prints "copy ctor": OK

trace t2;
wrapper<trace> w_2 {std::move(t2)}; // prints "move ctor": OK

wrapper<trace> w_3 {trace()}; // prints "move ctor": why?
}

我希望自己的 wrapper完全没有开销。特别是,当像 w_3一样将临时文件编码到包装器中时,我希望可以直接在适当位置创建 trace对象,而不必调用move ctor。但是,有一个move ctor调用,这使我认为创建了一个临时对象,然后将其从中移出。为什么叫移动ctor?怎么不称呼它呢?

最佳答案

I would expect the trace object to be created directly in place, without having to call the move ctor.



我不知道你为什么要这样。转发正是这样做的:移动或复制1)。在您的示例中,您使用 trace()创建一个临时目录,然后转发将其移至 x

如果要就地构造 T对象,则需要将参数传递给 T的构造,而不是要移动或复制的 T对象。

创建一个就位的构造函数:
template <class... Args>
wrapper(std::in_place_t, Args&&... args)
:x{std::forward<Args>(args)...}
{}

然后这样称呼它:
wrapper<trace> w_3 {std::in_place};
// or if you need to construct an `trace` object with arguments;
wrapper<trace> w_3 {std::in_place, a1, a2, a3};

解决OP对另一个答案的评论:

@bolov Lets forget perfect forwarding for a minute. I think the problem is that I want an object to be constructed at its final destination. Now if it is not in the constructor, it is now garanteed to happen with the garanteed copy/move elision (here move and copy are almost the same). What I don't understand is why this would not be possible in the constructor. My test case proves it does not happen according to the current standard, but I don't think this should be impossible to specify by the standard and implement by compilers. What do I miss that is so special about the ctor?



在这方面,ctor绝对没有什么特别的。您可以使用简单的免费函数看到完全相同的行为:
template <class T>
auto simple_function(T&& a)
{
X x = std::forward<T>(a);
// ^ guaranteed copy or move (depending on what kind of argument is provided
}

auto test()
{
simple_function(X{});
}

上面的示例与您的OP类似。您可以在 simple_function中将 x视为类似于包装构造函数,而将本地 wrapper变量视为与您的数据成员类似。就这一点而言,机制是相同的。

为了理解为什么不能直接在 simple_function的本地范围内构造对象(或在情况下作为包装对象的数据成员),您需要了解在C++ 17中保证复制省略是如何工作的,我建议 this excelent answer

总结一下答案:基本上,一个prvalue表达式不会实现一个对象,而是可以初始化一个对象的东西。在将表达式用于初始化对象之前,请保留尽可能长的时间(这样可以避免某些复制/移动)。请参阅链接的答案,以得到更深入但更友好的解释。

使用表达式初始化 simple_foo的参数(或构造函数的参数)的那一刻,您被迫具体化一个对象并丢失了表达式。从现在开始,您不再具有原始的prvalue表达式,而是创建了一个物化对象。现在,该对象需要移动到您的最终目标位置-我的本地 x(或您的数据成员 x)。

如果稍微修改一下示例,我们可以看到有保证的复制省略在起作用:
auto simple_function(X a)
{
X x = a;
X x2 = std::move(a);
}


auto test()
{
simple_function(X{});
}

没有省略,事情会像这样:
  • X{}创建一个临时对象作为simple_function的参数。让我们称之为Temp1
  • Temp1现在已移动(因为它是一个prvalue)到a
  • 的参数 simple_function
  • a被复制(因为a是左值)到x
  • a已移动(因为std::movea转换为xvalue)到x2

  • 现在有了C++ 17保证的复制省略
  • X{}不再当场实现对象。而是保留表达式。
  • 现在可以通过a表达式初始化simple_function的参数X{}。无需复制或移动,也不需要。

  • 其余部分现在相同:
  • a复制到x1
  • a已移至x2

  • 您需要了解的内容:命名某事后, 某物必须存在。令人惊讶的简单原因是,一旦您为某件事取了个名字,就可以多次引用它。看到我对这个 other question的回答。您已将参数命名为 wrapper::wrapper。我已将参数命名为 simple_function。那是您丢失prvalue表达式以初始化该命名对象的时刻。

    如果您想使用C++ 17保证的复制省略,而又不喜欢就地方法,则需要避免命名:)您可以使用lambda来做到这一点。我最常看到的成语是就地方式,包括在标准中。由于我还没有在野外看到过lambda方式,所以我不知道是否会推荐它。无论如何,这里:
    template<class T> class wrapper {
    public:

    template <class F>
    wrapper(F initializer)
    : x{initializer()}
    {}

    private:
    T x;
    };

    auto test()
    {
    wrapper<X> w = [] { return X{};};
    }

    在C++ 17中,此被授予者不进行任何复制和/或移动,并且即使 X删除了复制构造函数和move构造函数,它也起作用。就像您想要的那样,该对象将在其最终目的地被构造。

    1)我说的是正确使用的转发习语。 std::forward只是类型转换。

    关于c++ - 完美的转发和构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52498654/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com