将std::vector传递给构造函数及其移动语义

13
在周末,我正在尝试刷新我的C++技能并学习一些C++11。我遇到了以下问题:我无法强制我的容器类正确使用移动构造函数。
我有一个生成器类,定义如下:
class builder
{
   ...
   container build() const
   {
     std::vector<items> items;

     //... fill up the vector

     return container(items); //should move the vector right? wrong!
     //return container(std::move(items)); also doesn't work
   }
}

以下是类项和容器的定义:

class container
{
public:

    container(std:vector<item> items)
      : items_(items) // always invokes copy constructor on vector, never move
    { }

    container(container&& rhs)
    {
       ...
    }

    ...

private:
    std::vector<item> items_;

}

class item
{
public:
    //move .ctor
    item(item && rhs);
    item& operator=(item && rhs);

    //copy .ctor
    item(const item& rhs); //this gets called instead of move .ctor
    item& operator=(const item& rhs);

    ...
}

现在我的代码只是简单地使用了


builder my_builder;
...
auto result = my_builder.build();

这会导致每个项目首先被构建,然后再被复制...

我应该如何编写以下类以避免复制项目?我是否应该回到使用标准指针?


@Mankarse 这是一个打字错误,已经更正。 - ghord
2个回答

27

你的代码应该更改为这样:

container(std:vector<item2> items) // may copy OR move
: items_(std::move(items)) // always moves
{}
一般来说,如果你想要自己的一份拷贝,就在那个参数列表上制作这个拷贝,然后将其移动到需要的位置。让调用者决定是否复制或移动现有数据。(换句话说,你已经完成了一半,现在只需移动你的数据。)
此外:return container(std::move(items));。我之前没有提到这点,因为我错误地认为在返回语句中所有局部变量都会自动移动,但实际上只有返回值会被移动。(所以这应该可以工作:return items;,因为container的构造函数不是explicit)。

1
通常情况下,如果您要传递非常大且即使在移动时构造也很昂贵的对象,或者您正在编写通用代码,无论传递的对象类型如何都应该高效,则应通过右值引用(而不是按值传递)来传递参数。 - Mankarse
2
函数build中的返回语句应该改为return container(std::move(items));,否则会出现不必要的items拷贝。 - mark
@mark 你是对的。在返回语句中使用std::move是必须的,否则项目仍然会被复制。 - ghord
@Gregory:顺便问一下,使用哪个编译器?如果是GCC,那么我几乎可以确定不是所有对象都被移动了。 - GManNickG
@Mankarse:在任何我想谈论的代码中,移动构造都应该是默认构造之后最便宜的构造方法。我理解你的意思,但如果你的移动构造函数如此缓慢,问题不在于移动,而在于你的移动构造函数。(更不用说编译器应该内联构造函数代码,而不是实际进行两次移动了。) - GManNickG
显示剩余13条评论

6

我为你编写了这个启用移动功能的模板类。学习它,你就会明白。

/// <summary>Container.</summary>
class Container {
private:
    // Here be data!
    std::vector<unsigned char>  _Bytes;

public:
    /// <summary>Default constructor.</summary>
    Container(){
    }

    /// <summary>Copy constructor.</summary>
    Container(const Container& Copy){
        *this = Copy;
    }

    /// <summary>Copy assignment</summary>
    Container& operator = (const Container& Copy){
        // Avoid self assignment
        if(&Copy == this){
            return *this;
        }
        // Get copying
        _Bytes = Copy._Bytes; // Copies _Bytes
        return *this;
    }

    /// <summary>Move constructor</summary>
    Container(Container&& Move){
        // You must do this to pass to move assignment
        *this = std::move(Move); // <- Important
    }

    /// <summary>Move assignment</summary>
    Container& operator = (Container&& Move){
        // Avoid self assignment
        if(&Move == this){
            return *this;
        }
        // Get moving
        std::swap(_Bytes, Move._Bytes); // Moves _Bytes
        return *this;
    }
}; // class Container

我一直反对使用像这样的值参数:

function(std:vector<item2> items)

我通常使用以下两种方法之一:

function(const std:vector<item2>& items)
function(std:vector<item2>& items)
function(std:vector<item2>&& items)

特别是对于较大的数据容器,很少使用:

function(std:vector<item2> items)

对于较小的数据,永远不要使用向量。

这样做,您可以控制发生的一切,这也是我们使用C++的原因,用以掌控一切。

  • 如果需要可写副本,请在变量中自行复制const引用。
  • 如果需要只读副本,则const引用可防止新的复制品。
  • 如果想要编辑原始数据,请使用引用。
  • 当您感到懒惰时,对于小数据,请使用值参数。

显然,这一切都取决于您正在做什么。

我是一个自学的C++开发者。远非专家,特别是在C++俚语方面...但我在学习 :)


1
《C++11 中高效的参数传递》(Efficient Argument Passing in C++11)...讲解得非常好。 - CodeAngry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接