item 23: 通晓std::move和std::forward

本文翻译由《effective modern
C++》,由于水平有限,故无法担保翻译完全正确,欢迎提议错误。谢谢!

博客已经搬迁到这里啦

遵照std::move和std::forward不可知开呀来熟谙她是一个好点子。std::move没有move任何事物,std::forward没有转化任何东西。在运行期,它们并未召开其他工作。它们没有生需要举行之代码,一byte都没有。

std::move和std::forward只可是就是是进行cast的星星只函数(实际上是函数模板)。std::move无条件地拿它们的参数转换成一个右值,而std::forward只在特定条件知足的事态下实施这么些转换。就是如此了,我的讲以引申出同样层层之初题材,然而,基本上来说,下边说的虽是全体内容了。

为让内容越形象,那里叫来C++11遇std::move实现的一个例子。它从未完全依据标准的底细,可是好相近了。

template<typename T>                                //在命名空间std中
typename remove_reference<T>::type&&
move(T&& param)
{
    using ReturnType =                              //别名声明
        typename remove_reference<T>::type&&;       //看Item 9

    return static_cast<ReturnType>(param);
}

自家都协理你管代码的一定量个组成部分高亮(move和static_cast)呈现了。一个凡是函数的名字,因为归值类型挺复杂的,我非怀恋让你当当时纷繁的地方浪费时间。另一个地点是连了是函数的本来面目(cast)。就像而见到底那么,std::move需要一个对象的援(准确地即一个universal引用,看Item
24),并且重返跟一个靶的援。

函数重回值类型的“&&”部分暗示了std::move再次回到一个右值引用,然而,就像Item
28分解的这样,假设类型T恰好是左值引用,T&&将变为一个左值引用。为了防这样的事体时有暴发,type
trait(看Item
9)std::remove_reference于用在T上了,由此会管将“&&”加于无是引用的型上。这样能确保给std::move确切地回去一个右值引用,并且就是殊要紧之,因为出于函数再次回到的右值引用是一个右值。因而,std::move所召开的所有业务虽是变它的参数为一个右值。

说词题外话,在C++14面临std::move能让实现得重省心一些。多亏了函数重返值类型推导(看Item
3)以及标准库的号模板std::remove_reference_t(看Item
9),std::move能被勾勒成这样:

template<typename T>
decltype(auto) move(T&& param)
{
    using ReturnType = remove_reference_t<T>&&;
    return static_cast<ReturnType>(param);
}

看上去还简单了,不是吧?

为std::move值只换它的参数为右值,这里来一对再好的名,比如说rvalue_cast。尽管如此,我们如故接纳std::move作为其的名字,所以记住std::move做了什么与没有开啊特别要紧。它做的是易,没有开move。

自矣,右值是move的候选人,所以将std::move应用在靶及可知告编译器,这一个目的是来资格被move的。这为虽然是为何std::move有这样的讳:能叫指定的对象还便于吃move。

实际,右值是move的绝无仅有候选人。假设你写了一个象征注释的类似。这一个仿佛的构造函数有一个std::string的参数,并且其拷贝参数到一个数码成员中。依照Item
41饱受的信息,你表明一个传值的参数:

class Annotation {
public:
    explicit Annotation(std::string text);      // 要被拷贝的参数
                                                // 根据Item 41,声明为传值的
    ...
};

但Annotation的构造函数只需要读取text的值。它不需改其。为了契合历史习俗(把const用当任何可以用的地点),你改改了而的讲明,因而text成为了const的:

class Annotation {
public:
    explicit Annotation(const std::string text)
    ...
};

为在拷贝text到数成员的时段不将时间浪费在拷贝操作上,你保持Item
41的指出还要将std::move用在text上,由此发生了一个右值:

class Annotation {
public:
    explicit Annotation(const std::string text)
    :value(std::move(text))     // “move” text到value中去;这段代码
    {...}                           //做的事情不像看上去那样

    ...

private:
    std::string value;
};

代码可以编译。代码可以链接。代码能够推行。代码把数量成员value的值设为text的情节。这段代码和圆的代码(你所设的版)之间的绝无仅有不同之处就是text不是叫move到value中失去的,它是拷贝过去的。当热,text通过std::move转换成为了一个右值,然则text被声称也一个const
std::string,所以当变在此之前,text是一个左值const
std::string,然后变的结果就是是一个右值const
std::string,不过从来到终极,const属性保留下了。

考虑一下const对于编译器决定调用哪个std::string构造函数有啊震慑。那里有点儿种或:

class string {                      // std::string实际上是
public:                             // std::basic_string<char>的一个typedef
    ...
    string(const string& rhs);      // 拷贝构造函数
    string(string& rhs);            // move构造函数
    ...
};

以Annotation的构造函数的积极分子初步化列表中,std::move(text)的结果是一个const
std::string的右值。这多少个右值未可知传于std::string的move构造函数,因为move构造函数只接受非const
std::string的右值引用。可是,那些右值能被传染被拷贝构造函数,因为一个lvalue-reference-to-const(引用const的左值)能叫绑定到一个const右值上去。由此虽然text已经让转正成了一个右值,成员先河化列表依然调用了std::string中的正片构造函数。这样的所作所为实为上是为了保障const的科学。一般将一个值move出去就一定给改动了这目的,所以C++不容许const对象为传为一个能转其本身的函数(比如move构造函数)。

我们由这多少个例子中获两独教训。第一,倘使您想只要受一个靶会给move,就不用把那一个目的表明也const。在const对象及之move请求会让默认地转换成为拷贝操作。第二,std::move事实上远非move任何事物,它竟然不克保证她换出来的靶子会生资格被move。你唯一可以精晓之政工就是,把std::move用在一个靶后,它成为了一个右值。

std::forward的景观及std::move相类似,可是std::move是无偿地把它的参数转换成右值的,而std::forward只当规定标准下才这样做。std::forward是一个有规则的变。为了精晓它们什么时候换,什么时不换,记忆一下std::forward是怎使用的。最广大的景便是,一个带universal引用的参数为传染被其余一个参数:

void process(const Widget& lvalArg);            // 参数为左值
void process(Widget&& rvalArg);                 // 参数为右值

template<typename T>                            // 把参数传给process
void logAndProcess(T&& param)                   // 的模板
{
    auto now =
        std::chrono::system_clock::now();       // 取得正确的时间

        makeLogEntry("Calling 'process'", now);
        process(std::forward<T>(param));
}

考虑一下四个logAndProcess调用,一个应用左值,另外一个应用右值:

Widget w;

logAndProcess(w);               // 用左值调用
logAndProcess(std::move(w));    // 用右值调用

在logAndProcess内部,参数param被传染被process函数。process重载了左值和右值五只版本。当大家为此左值调用logAndProcess的时刻,大家本来是期以此左值作为一个左值被转发让process,然后当我们应用右值调用logAndProcess时,大家要右值版本的process被调用。

唯独param就跟持有的函数参数一样,是一个左值。由此当logAndProcess内部总是调用左值版本的process。为了防范这样的事务发,我们用平等栽机制来深受param在它被一个右值伊始化(传被logAndProcess的参数)的上换成右值。这刚好就是是std::forward做的工作。这吗就是是干什么std::forward是一个标准化换:它唯有将用右值开端化的参数转换成右值。

乃或会晤飞std::forward怎么亮他的参数是未是故右值初步化的。举个例子吧,在地方的代码中,std::forward怎么会精通param是被左值如故右值开头化的吗?简单的话就是是那个音信于含有在logAndProcess的模版参数T中了。这几个参数为染于了std::forward,那样就是吃std::forward得知了这信息。它有血有肉怎么工作之底细要参考Item
28。

设想到std::move和std::forward都于概括为换,不同之处就是std::move总是执行转换,可是std::forward只以聊情形下实施转换,你可能会师咨询大家是未是可错过丢std::move并且于享有的地点都只有下std::forward。从技术的角度来拘禁,回答是好:std::forward能成功所有的作业。std::move不是必的。当然,这半个函数函数都不是“必须的”,因为大家会于运用的地点写cast,可是我要我们可以允许它是必的函数,好吧,真是令人不快的从。

std::move的长是便利,裁减一般之荒谬,并且愈来愈清晰。考虑一个类,对于这近乎大家记念要记录其的move构造函数被调用了聊坏。一个能以move构造的时节自增的static计数器就是大家需要的东西了。要是是仿佛中绝无仅有的非static数据是一个std::string,这里吃出常常的法门(也就是是使std::move)来兑现move构造函数:

class Widget {
public:
    Widget(Widget&& rhs)
    : s(std::move(rhs.s))
    { ++moveCtorCalls;}
}

...

private:

 static std::size_t moveCtorCalls;
 std::string s;
};

为用std::forward来促成同之一言一行,代码看起如是这么的:

class Widget {
public:
    Widget(Wdiget&& rhs)                    //不常见,以及不受欢迎的实现
    : s(std::forward<std::string>(rhs.s))
    //译注:为什么是std::string请看Item 1,用右值传入std::string&& str的话
    //推导的结果T就是std::string,用左值传入,则推导的结果T会是std::string&
    //然后这个T就需要拿来用作forward的模板类型参数了。
    //详细的解释可以参考Item28
    { ++moveCtorCalls; }
};

先是注意std::move只需要一个函数参数(rhs.s),而std::forward却要一个函数参数(rhs.s)以及一个模板类型参数(std::string)。然后小心一下大家传给std::forward的门类应该是一个勿引用类型,因为大家约定好传入右值的早晚倘使这样编码(传入一个免引用类型,看Item
28)。也就是说,这意味std::move需要输入的东西比std::forward更少,还有,它去丢了我们传入的参数是右值时之难为(记住类型参数的编码)。它吗消除了大家传入错误类型(比如,std::string&,这会招数据成员用拷贝构造函数来替换move构造函数)的或许。

进一步要之是,使用std::move表示无条件转换来一个右值,然后下std::forward表示除非引用的凡右值时才更换到右值。这是少数种万分差之所作所为。第一独平常执行move操作,可是第二个只是传递(转发)一个目的为其余一个函数并且保留其原有之左值属性或右值属性。因为这多少个行为如此地不同,所以我们下简单个函数(以及函数叫)来区分它们是非凡好之主见。

            你要铭记在心的从事
  • std::move执行到右值的白转换。就该自我而言,它没有move任何事物。
  • std::forward只有在其的参数绑定到一个右值上之时节,它才转移它的参数到一个右值。
  • std::move和std::forward在运行期都尚未做任何事情。