41

我想知道自 C++11 以来在哪些情况下我仍需要在参数中使用 const 引用。我不完全理解移动语义,但我认为这是一个合法的问题。这个问题仅适用于 const 引用替换正在制作的副本而只需要“读取”值(例如 const 成员函数的使用)的情况。

通常我会写一个像这样的(成员)函数:

#include <vector>

template<class T>
class Vector {
    std::vector<T> _impl;
public:
    void add(const T& value) {
         _impl.push_back(value);
    }
};

但是我认为如果我这样写并且class T当然实现了一个移动构造函数,编译器会使用移动语义来优化它是安全的:

#include <vector>

template<class T>
class Vector {
    std::vector<T> _impl;
public:
    void add(T value) {
         _impl.push_back(value);
    }
};

我对吗?如果是这样,假设它可以在任何情况下使用是否安全?如果没有,我想知道是哪个。这将使生活变得更轻松,因为我不必为基本类型实现类专业化,而且它看起来更干净。

4

1 回答 1

46

您提出的解决方案:

void add(T value) {
     _impl.push_back(value);
}

需要一些调整,因为这样你总是最终执行一个副本value,即使你将一个右值传递给add()(如果你传递一个左值,则两个副本):因为value是一个左值,编译器不会自动从它移动你将它作为参数传递给push_back.

相反,您应该这样做:

void add(T value) {
     _impl.push_back(std::move(value));
//                   ^^^^^^^^^
}

这更好,但对于模板代码仍然不够好,因为您不知道T移动是便宜还是昂贵。如果T是这样的 POD:

struct X
{
    double x;
    int i;
    char arr[255];
};

然后移动它不会比复制它快(事实上,移动它和复制它是一样的)。因为您的通用代码应该避免不必要的操作(这是因为这些操作对于某些类型来说可能很昂贵),所以您不能按值获取参数。

一种可能的解决方案(C++ 标准库采用的一种)是提供两种重载add(),一种采用左值引用,另一种采用右值引用:

void add(T const& val) { _impl.push_back(val); }
void add(T&& val) { _impl.push_back(std::move(val)); }

另一种可能性是提供一个(可能受 SFINAE 约束的)完美转发模板版本,add()该版本将接受所谓的通用参考(Scott Meyers 创造的非标准术语):

template<typename U>
void add(U&& val) { _impl.push_back(std::forward<U>(val)); }

这两种解决方案都是最优的,因为当提供左值时只执行一次复制,而当提供右值时只执行一次移动。

于 2013-07-14T22:24:50.703 回答