c++11的move forward(二)特殊使用规则

通用引用版本和右值引用版本的一些异同比较

假定要实现一个person类,并且实现一个设置person name的方法setName。
第一种,通用引用版本(对通用引用使用std::forward)

class person
{
public:
    template<class T>
    void setName(T&& name)
    {
        name_ = std::forward<T>(name);
    }
private:
    std::string name_;
};

第二种,重载左值引用和右值引用版本

class person
{
public:
    void setName(const std::string& name)
    {
        name_ = name;
    }
    void setName(std::string&& name)
    {
        name_ = std::move(name);
    }
private:
    std::string name_;
};

(1)非const左值引用只能绑定到非const左值;
(2)const左值引用可绑定到const左值、非const左值、const右值、非const右值;
(3)非const右值引用只能绑定到非const右值;
(4)const右值引用可绑定到const右值和非const右值。

两种版本比较:

  1. 万能引用版本传入参数不能是const。如果传入的name参数是不可更改的,const左值引用版本会更符合。
    更正:以上理解错误。根据模板类型推导,万能引用版本如果传入const左值引用,T和ParamType都会被推到为const左值引用类型。无论是const左值引用还是非const左值引用传入后都为const左值引用类型。而万能引用版本非const左值引用传入参数还是非const左值引用。所以重载左值版本只是为了将传入的参数加上const属性限制。

  2. 考虑一种使用场景:

person p;
p.setName("abc");

如果是通用引用版本,字符串"abc"是一个右值,T会被推导为const char*类型,parmtype推导为const char* &&类型,直接将字符串"abc"传入函数,调用std::string的赋值操作,期间没有产生std::string的临时对象。
如果是重载右值引用的版本,字符串"abc"会首先构造一个临时string对象传入,然后调用string的移动赋值操作(右值版本的赋值操作符)。最后还有一次临时string的对象析构。这样的开销肯定要比通用引用版本的开销要大。

c++11 stl中容器新增的emplace_方法其实就是用的万能引用版本。
例如vector的emplace_back方法:

template <class... Args>
  void emplace_back (Args&&... args);

我们大概可以猜测emplace_back内部也应该用了forward方法。

谨慎地对通用引用构造函数进行重载

class person
{
public:
    template<class T>
    person(T&& t) : name_(std::forward<T>(t)) {}

    person(const person& p) {} // 编译器自动生成的拷贝构造

	person(person&& p) {} // 编译器自动生成的移动构造

    template<class T>
    void setName(T&& name)
    {
        name_ = std::forward<T>(name);
    }
private:
    std::string name_;
};

一个通用引用构造,编译期自动生成的拷贝构造和移动构造。
考虑下面这种用法,

    person a("abc");
    person b(a);

我们期望person b(a); a是一个左值,这句代码会调用拷贝构造,但实际上会编译出错。
因为a是非const类型,优先匹配了万能引用的构造函数,T被识别为person&类型。又因为string没有参数类型为person&的构造函数类型,导致编译出错。
所以应该谨慎地对通用引用构造函数进行重载。
另外当通用引用和普通重载匹配度一样时,优先调用普通重载函数。

返回值语义

  1. 如果你有个函数是通过值返回,然后你函数内返回的是被右值引用或通用引用绑定的对象,那么你应该对你返回的对象使用std::move或std::forward。
class mystring
{
public:
    mystring(const char* str) : str_(str)
    {
        std::cout << "mystring(const char* str)" << std::endl;
    }

    mystring(const mystring& ms)
        : str_(ms.str_)
    {
        std::cout << "mystring(const mystring& ms)" << std::endl;
    }

    mystring(mystring&& ms)
        : str_(std::move(ms.str_))
    {
        std::cout << "mystring(mystring&& ms)" << std::endl;
    }

    mystring& operator+=(const mystring rhs)
    {
        std::cout << "operator+=" << std::endl;
        str_ += rhs.str_;
        return *this;
    }
private:
    std::string str_;
};

mystring mystrcat1(mystring&& str1, const mystring& str2)
{
    str1 += str2;
    return str1;
}

mystring mystrcat2(mystring&& str1, const mystring& str2)
{
    str1 += str2;
    return std::move(str1);
}

比较mystrcat1和mystrcat2调用结果,
如果是不用std::move或者std::forward,会调用拷贝构造拷贝到返回区,而如果是用std::move或者std::forward,会调用移动构造移动到返回区。假如参数类型支持移动构造,它比拷贝构造效率更高,那么在返回语句中使用std::move会产生更高效的代码。如果参数类型不支持移动构造,把它转换为右值也是无伤害的,因为此时右值会简单地作为参数来调用拷贝构造函数。
stl中basic_string类的operator+函数就是这种用法。

template <class _Elem, class _Traits, class _Alloc>
_NODISCARD inline basic_string<_Elem, _Traits, _Alloc> operator+(basic_string<_Elem, _Traits, _Alloc>&& _Left,
    _In_z_ const _Elem* const _Right) { // return string + NTCTS
    return _STD move(_Left.append(_Right));
}
  1. 如果是函数内的局部变量,因为C++编译器都会进行RVO优化来避免拷贝,所以无需std::move或std::forward。对局部变量用了std::move或std::forward反而会阻碍RVO优化,得不偿失。
    参考:https://blog.csdn.net/big_yellow_duck/article/details/52388820
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值