【问题标题】:How do I utilize move semantics when returning a member variable?返回成员变量时如何利用移动语义?
【发布时间】:2013-10-08 15:17:16
【问题描述】:

我正在实现一个构建 uint8_t 向量的工厂类。我希望能够在返回结果向量时利用移动语义。这似乎可行,但我不确定这是完成我想要的正确方法。

我已经看过很多返回的自动变量如何被视为右值并使用调用代码的移动构造函数的示例,但在我的示例中,返回的对象是一个成员。我知道如果调用者将返回值放入移动构造函数中,成员将丢失其内容 - 这正是我想要的。

我是这样写的:

#include <cstdint>
#include <iostream>
#include <vector>

class Factory
{
public:
    std::vector<uint8_t> _data;

    Factory(std::size_t size) :
        _data(size, 0)
    {
    }

    void buildContent(int param)
    {
        // perform operations on the contents of _data
    }

    std::vector<uint8_t> && data()
    {
        return std::move(_data);
    }
};

int main()
{
    Factory factory(42);
    factory.buildContent(1);
    std::vector<uint8_t> temp(factory.data());
    std::cout << "temp has " << temp.size() << " elements" << std::endl;
    std::cout << "factory._data has " << factory._data.size() << " elements" << std::endl;

    return 0;
}

编辑:

哦,示例代码输出如下:

temp has 42 elements
factory._data has 0 elements

【问题讨论】:

  • 那么你的问题是什么?
  • “我不确定这是实现我想要的正确方法。”
  • 哦,我现在明白了。看看这个:stackoverflow.com/questions/9914548/…
  • 这和你的问题直接相关:stackoverflow.com/questions/5770253/…
  • 我会把move这个词放在你的data方法名中。如factory.move_data()。此外,返回 std::vector 实例而不是 &amp;&amp; 可能是值得的,因为您可以以极少的运行时成本获得更好的生命周期行为。

标签: c++ c++11 move-semantics


【解决方案1】:

首先,你必须决定你想要什么。你真的想从你的实例中吸取数据吗?如果是这样,那么您所拥有的就可以了。另一方面,它看起来很不安全。你可以做的是使用引用限定符来强制你只在实例本身是一个右值引用时才返回一个右值引用:

std::vector<uint8_t> && data() && // called on rvalue
{
    return std::move(_data);
}

// return lvalue ref or value
std::vector<uint8_t>& data() & // called on lvalue
{
    return _data;
}

最后,这完全取决于您要解决的问题,这在您的问题中并不明显。

【讨论】:

  • 有支持这种语法的编译器吗? (&& 在方法声明之后)
  • @BЈовић Apparently, GCC 4.8.1,虽然我还没有测试过。
【解决方案2】:

如果您的编译器对此有右值引用(&amp;&amp; after 方法),您可能想要使用它。请参阅@juanchopanza 的回答。

如果您不这样做,您首先要确保 data() 清楚地表明您正在移动。有几种方法可以做到这一点。

首先,非成员方法 (friends) 可以覆盖 &amp;&amp;。所以你可以得到这个语法:

std::vector<uint8_t> temp(get_data( std::move(factory) );

其中get_data 在您的factory 类型上有&amp;&amp;&amp; 重载,并且基于它移动或不移动。

接下来,您希望返回 std::vector&lt;uint8_t&gt; 而不是 `std::vector&lt;uint8_t&gt;&amp;&amp; 以解决生命周期延长问题。运行时成本介于零和很小之间,但消除的错误是值得的。

如果create_factory 返回一个Factory 对象,那么如果我们这样做:

for( uint8_t x : get_data( create_factory() ) )

返回 &amp;&amp;get_data 不起作用,而返回临时值的 get_data 工作正常。

发生了什么事?好吧,基于范围的 for 循环被定义为将您正在迭代的内容绑定到引用。临时绑定到引用的生命周期延长:绑定到引用的引用没有生命周期延长。在这两种情况下,create_factory 函数的返回值的生命周期都不会延长。

&amp;&amp; 的情况下,对向量的引用是悬空的。在 return-temporary 的情况下,factory 的向量被移动到临时的,然后临时的生命周期被延长。

简而言之,返回 &amp;&amp; 引用很少是一个好主意。

【讨论】:

    【解决方案3】:

    如何将赋值运算符隐藏在向量中并仅实现移动赋值运算符(本质上不允许复制 - 如果这是你想要的)。

    这样:

    std::vector<uint8_t> temp(factory.data());
    

    除非你把它改成这样,否则不会编译:

    std::vector<uint8_t> temp(std::move(factory));
    

    【讨论】:

    • 如何隐藏 std::vector 的复制构造函数或赋值运算符?
    • 我的意思是,如果您通过继承(IS-A 向量)或包装(HAS-A 向量)您的工厂来更改工厂的含义,使其更像向量的扩展然后客户将与工厂打交道(您必须重命名以反映新含义)。或者,您可以创建一个不可复制的向量并从工厂返回它而不是 std::vector 但这听起来可能太复杂了。您的方法的问题是对 facotry.data() 的调用将使该数据无效,这可能会令人困惑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    • 2019-07-28
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    相关资源
    最近更新 更多