gpt4 book ai didi

c++ - 为什么 std::tuple 的 get helper 返回右值引用而不是值

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:34:07 25 4
gpt4 key购买 nike

如果您查看 getstd::tuple 的辅助函数,您会注意到以下重载:

template< std::size_t I, class... Types >
constexpr std::tuple_element_t<I, tuple<Types...> >&&
get( tuple<Types...>&& t );

换句话说,当输入元组本身是一个右值引用时,它返回一个右值引用。为什么不按值返回,在函数体中调用move?我的论点如下:get 的返回将绑定(bind)到一个引用或一个值(我想它可以绑定(bind)到任何东西,但这不应该是一个常见的用例)。如果它绑定(bind)到一个值,那么 move 构造无论如何都会发生。因此,按值(value)返回不会有任何损失。如果你绑定(bind)到一个引用,那么返回一个右值引用实际上是不安全的。举个例子:

struct Hello {
Hello() {
std::cerr << "Constructed at : " << this << std::endl;
}

~Hello() {
std::cerr << "Destructed at : " << this << std::endl;
}

double m_double;
};

struct foo {
Hello m_hello;
Hello && get() && { return std::move(m_hello); }
};

int main() {
const Hello & x = foo().get();
std::cerr << x.m_double;
}

运行时,该程序打印:

Constructed at : 0x7ffc0e12cdc0
Destructed at : 0x7ffc0e12cdc0
0

换句话说,x 直接是一个悬空引用。而如果你只是像这样写 foo:

struct foo {
Hello m_hello;
Hello get() && { return std::move(m_hello); }
};

不会出现这个问题。此外,如果您随后像这样使用 foo:

Hello x(foo().get());

无论是按值返回还是按右值引用返回,似乎都没有任何额外开销。我已经测试过这样的代码,它似乎会非常一致地只执行一次 move 构造。例如。如果我添加一个成员:

  Hello(Hello && ) { std::cerr << "Moved" << std::endl; }

并且我按上面的方式构造 x,无论我是按值返回还是按右值引用返回,我的程序只打印一次“move ”。

我失踪是有充分的理由,还是疏忽?

注意:这里有一个很好的相关问题:Return value or rvalue reference? .似乎说在这种情况下返回值通常更可取,但它出现在 STL 中的事实让我很好奇 STL 是否忽略了这个推理,或者他们是否有自己的特殊原因可能不适用一般。

编辑:有人建议此问题与 Is there any case where a return of a RValue Reference (&&) is useful? 重复.不是这种情况;这个答案建议通过右值引用返回作为省略数据成员复制的一种方式。正如我在上面详细讨论的那样,如果您先调用 move,则无论您是按值返回还是按右值引用返回,复制都将被省略。

最佳答案

关于如何使用它来创建悬挂引用的示例非常有趣,但从示例中吸取正确的教训很重要。

考虑一个更简单的例子,它没有任何 &&任何地方:

const int &x = vector<int>(1) .front();

.front()返回 & - 对新构造 vector 的第一个元素的引用。该 vector 当然会立即被销毁,您会留下一个悬垂的引用。

要吸取的教训是使用 const -reference 通常不会延长生命周期。它延长了非引用 的生命周期。如果 = 的右侧是一个引用,那么你就得自己对生生世世负责。

情况一直如此,所以对于 tuple::get 没有意义做任何不同的事情。 tuple::get允许返回引用,就像 vector::front一直都是。

您谈到了 move 和复制构造函数以及速度。最快的解决方案是不使用任何构造函数。想象一个连接两个 vector 的函数:

vector<int> concat(const vector<int> &l_, const vector<int> &r) {
vector<int> l(l_);
l.insert(l.end(), r.cbegin(), r.cend());
return l;
}

这将允许优化的额外重载:

vector<int>&& concat(vector<int>&& l, const vector<int> &r) {
l.insert(l.end(), r.cbegin(), r.cend());
return l;
}

这种优化使构造的数量保持在最低水平

   vector<int> a{1,2,3};
vector<int> b{3,4,5};
vector<int> c = concat(
concat(
concat(
concat(vector<int>(), a)
, b)
, a
, b);

最后一行,四次调用 concat , 将只有两种构造:起始值 ( vector<int>() ) 和 move 构造到 c .您可以对 concat 进行 100 次嵌套调用那里,没有任何额外的 build 。

因此,通过 && 返回可以更快。因为,是的, move 比复制更快,但如果你能避免两者,它甚至更快。

总而言之,这样做是为了速度。考虑使用 get 的嵌套系列在元组内的元组上。此外,它允许它使用既没有复制也没有 move 构造函数的类型。

而且这不会引入任何关于生命周期的新风险。 vector<int>().front() “问题”并不是一个新问题。

关于c++ - 为什么 std::tuple 的 get helper 返回右值引用而不是值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31359829/

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