用C++11的方式从参数初始化数据成员

20

既然C++11支持移动语义,那么在从参数初始化数据成员时,我们是否应该尝试移动值而不是复制它?

以下是一个示例,展示了在C++11之前我如何处理这个问题:

struct foo {
    std::vector<int> data;

    explicit foo(const std::vector<int>& data)
        : data(data)
    {
    }
};

在这里,将调用复制构造函数。

在C++11中,我们应该养成像这样编写代码的习惯吗:

struct foo {
    std::vector<int> data;

    explicit foo(std::vector<int> data)
        : data(std::move(data))
    {
    }
};

在这里,如果传递的参数是左值,移动构造函数和复制构造函数都会被调用,但好处是如果传递的是右值,移动构造函数会被调用而不是复制构造函数。

我想知道是否还有什么我没有考虑到的东西。

4个回答

8

我对你的问题的最初回答是:

不要复制你想要移动的数据。如果性能是一个问题,你可以使用右值引用添加构造函数:

explicit foo(std::vector<int>&& data)
    : data(std::move(data))            // thanks to Kerrek SB
{
}

虽然不完全符合您的问题,但阅读这篇文章似乎很有用: Rule-of-Three becomes Rule-of-Five with C++11?

编辑:

然而,对于构造函数传递/移动参数, Passing/Moving parameters of a constructor in C++0x 的接受答案似乎支持您的方法,特别是有多个参数时。 否则将会出现组合爆炸。


3
不过你仍需要写成 data(std::move(data)) - Kerrek SB
我会接受这个答案,因为有安心的链接。 - someguy

3

是的,你做得很正确。任何时候需要一个值的副本,都应该通过传递值的方式在参数中实现。

以下是正确的:

struct foo {
    std::vector<int> data;

    explicit foo(std::vector<int> data)
        : data(std::move(data))
    {
    }
};

3

在复制构造函数中按值传递仅在参数可移动时有帮助,否则可能会导致最多两个副本(一个用于参数传递,一个用于成员构造)。因此,我认为最好分别编写复制构造函数和移动构造函数。

如果您有一个正确实现的swap函数,则按值传递对于赋值运算符是有意义的:

Foo & operator=(Foo other) { this->swap(std::move(other)); }

现在,如果 other 是可移动的,Foo 的移动构造函数在参数构造期间被调用,而如果 other 只是可复制的,则在参数构造期间只进行必要的一次复制,但在两种情况下,您都可以使用移动版本的 swap,这应该是很便宜的。但是这取决于是否存在移动构造函数!
因此,请注意,在“构造”、“交换”和“赋值”之中,您将必须正确地实现两个,并且只有第三个可以利用其他两个。由于 swap 应该是无异常的,因此在赋值运算符中使用交换技巧基本上是唯一的选择。

2
一个不错的编译器(选择任何知名的编译器)将省略复制,因此即使参数不可移动,也只会发生一次复制。 - someguy

0

你应该坚持使用:

struct foo {
    std::vector<int> data;

    explicit foo(const std::vector<int>& data)
        : data(data)
    {
    }
};

在这种情况下,“数据”只是被复制。
在第二种情况下:
struct foo {
    std::vector<int> data;

    explicit foo(std::vector<int> data)
        : data(std::move(data))
    {
    }
};

"

"data"首先被复制,然后移动。这比仅仅复制要更昂贵。请记住,即使移动可能比复制便宜得多,但移动也不是免费的。

另一方面,您可以考虑添加以下内容(除了或代替第一个)。

"
struct foo {
    std::vector<int> data;

    explicit foo(std::vector<int>&& data)
        : data(std::move(data))
    {
    }
};

如果你知道在构造函数调用后不会再使用“数据”,那么你可以将其移动。


2
我在另一个回答中的评论是:“一个不错的编译器(选择任何知名的编译器)将省略复制操作,因此即使参数不可移动,也只会发生一次复制。” 我考虑过使用两个独立的构造函数,但这可能有点过头了,尤其是对于多种类型的构造函数。 - someguy
在第二种情况下,如果构造函数传递了一个 rvalue,它不会被复制,只会被移动或省略。通过使用 const 引用传递它,即使不必要,也会强制进行复制。 - Benjamin Lindley

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