Const引用VS移动语义

问题描述 投票:31回答:1

我想知道在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 ofcourse实现了移动构造函数:

#include <vector>

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

我对吗?如果是这样,假设它可以在任何情况下使用是否安全?如果没有,我想知道哪个。这将使生活变得更加容易,因为我不必为基本类型实现类专门化,例如,它看起来更清晰。

c++11 const move-semantics
1个回答
29
投票

你提出的解决方案:

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

需要一些调整,因为这样你总是会执行value的一个副本,即使你将rvalue传递给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)); }

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

© www.soinside.com 2019 - 2024. All rights reserved.