C++11右值语义与类型转换

本文探讨了C++11中右值语义在提升性能和减少拷贝开销中的作用,通过实例展示了移动构造与赋值的使用,深入解析了深浅拷贝的区别。讲解了左值、右值和右值引用的概念,以及如何通过移动构造和std::move实现纯右值与将亡值的优化。此外,还涉及函数参数和返回值的优化技巧,包括万能引用和完美转发,以及四种新的类型转换操作符的应用。
摘要由CSDN通过智能技术生成


右值是C++11中新出现的特性,可以提高性能,减少拷贝开销。

右值语义

有移动构造与赋值的类示例:

class MyString
{
public:
    MyString() : m_pData(NULL), m_nLen(0)
    {
        cout << "Default constructor" << endl;
    }
    MyString(const char *pStr) // 允许隐式转换
    {
        cout << "Constructor(const char *)" << endl;
        m_nLen = strlen(pStr);
        CopyData(pStr);
    }
    MyString(const MyString &other)
    {
        cout << "Assign constructor(&)" << endl;
        if (!other.m_pData)
        {
            m_nLen = other.m_nLen;
            DeleteData();
            CopyData(other.m_pData);
        }
    }
    MyString(MyString &&other)
    {
        cout << "Move constructor(&&)" << endl;
        m_nLen = other.m_nLen;
        m_pData = other.m_pData;
        other.m_pData = NULL;
    }

    MyString &operator=(const MyString &other)
    {
        cout << "Assign operator=(&)" << endl;
        if (this != &other)
        {
            m_nLen = other.m_nLen;
            DeleteData();
            CopyData(other.m_pData);
        }

        return *this;
    }
    MyString &operator=(MyString &&other)
    {
        cout << "Move operator=(&&)" << endl;
        if (this != &other)
        {
            m_nLen = other.m_nLen;
            m_pData = other.m_pData;
            other.m_pData = NULL;
        }

        return *this;
    }

    void Output() const
    {
        cout << "Output: " << (m_pData ? m_pData : "<NULL>") << endl;
    }

    ~MyString()
    {
        cout << "Destructor: " <<  (m_pData ? m_pData : "<NULL>")  << endl;
        DeleteData();
    }

private:
    void CopyData(const char *pData)
    {
        if (pData)
        {
            m_pData = new char[m_nLen + 1];
            memcpy(m_pData, pData, m_nLen);
            m_pData[m_nLen] = '\0';
        }
    }

    void DeleteData()
    {
        if (m_pData != NULL)
        {
            delete[] m_pData;
            m_pData = NULL;
        }
    }

private:
    char *m_pData;
    size_t m_nLen;
};

MyString Fun()
{
    MyString str = "hello world";
    return str;
}

void funRef(const MyString &str)
{
    str.Output();
}

void funMove(MyString&& str){
    auto str1 = std::move(str);	// 必须使用move才会引发移动构造
    str.Output();
    str1.Output();
}

int main()
{
    auto str = Fun();
    cout<<"####To call Ref"<<endl;
    funRef(str);
    cout<<"####To call Move"<<endl;
    funMove(std::move(str));

    return 0;
}
// Constructor(const char *)
// ####To call Ref
// Output: hello world
// ####To call Move
// Move constructor(&&)
// Output: <NULL>
// Output: hello world
// Destructor: hello world
// Destructor: <NULL>

深浅拷贝

对象拷贝时,会涉及到浅拷贝(shallow copy)和深拷贝(deep copy):

  • 浅拷贝:按位拷贝对象,新的对象与原对象属性值精准相同(指针也指向同一块内存);
  • 深拷贝:拷贝所有的属性(若是指针,则申请一块新的内存);

一般情况下,深拷贝比浅拷贝开销大;但浅拷贝存在一个严重的问题:当有指针时,会造成多个对象共用一块内存,导致冲突。

但是若能保证原对象不再访问这块内存(如临时对象),则就不存在冲突了。

左值与右值

C++很早就有左值与右值的概念:

  • 左值lvalue(loactor value,可寻址的数据):表达式结束后依然存在的对象;
  • 右值rvalue(read value,可获取值的数据):表达式结束后不再存在的对象;

字面量(字符字面量除外,其存放在静态存储区,持久存在)、临时表达式、临时的函数返回值等都是右值。一般情况下,有变量名的对象为左值,无变量名的为右值。

右值引用

为匹配右值,C++11中引入了右值引用类型&&;并增加了移动构造、赋值函数(移动,即转移所有权,表明原对象是个临时对象)。

为把某些左值(如很快会离开作用域的左值)匹配成右值,提供了std::move<T>函数,把左值强制转换为右值,以匹配右值引用类型。

纯右值与将亡值

C++11中对左值、右值类别进行了重新定义;每个C++表达式只属于基本类别中的一种:

  • 左值(lvalue)表达式:拥有身份(非临时对象)且不可被移动的表达式;
  • 将亡值(xvalue)表达式:拥有身份,但可被移动(可被右值引用类型匹配)的表达式;
  • 纯右值(prvalue)表达式:不拥有身份且可被移动的表达式(纯粹的临时对象);

函数

函数参数与返回值也会涉及到右值优化的问题:

  • 首先要编写类型的移动构造与赋值函数,让参数自然而然地享受右值移动的优化效果;
  • 若参数支持移动构造,在提供左值引用的同时可提供右值引用版本(移动开销很低时,可除外);
  • 不要编写返回右值引用类型的函数(特殊情况除外)。

万能引用

万能引用(Universal Reference):

  • 发送类型推导(如模板、auto)时,使用T&&表示万能引用;其他情况表示右值引用;
  • 万能引用类型的形参能匹配对应类型的左值与右值。

注意:万能引用参数的函数为贪婪函数,容易让需要隐式转换的实参匹配到不希望的转发引用函数。

如下:非int类型不会因隐式转换而去匹配fun(int),而是直接匹配了模板引用类型。

template<class T> void fun(T&& value);
void fun(int v);

short s=0;
int i=0;
long l=0;
fun(s); // 匹配模板 &&
fun(i);
fun(l); // 匹配模板 &&

引用折叠

当出现引用嵌套时,C++会通过引用折叠规则保证要么是左值,要么是右值:

引用折叠&&&
&&&
&&&&&

完美转发

使用万能引用即可以同时匹配左值、右值;但需要转发参数给其他函数时,会丢失引用性质(形参是个左值,无法判断到底匹配的是个左值还是右值)。为此需要完美转发函数std::forward<T>将参数类型保持原样传入,forward一般用于模板函数:

template<typename T>
void func(T&& val){
    realFun(std::forward<T>(val));
}

类型转换

C++11标准中新增了四个新的类型转换操作符:

  • const_cast:用于增加或去除const、volatile修饰;
  • static_cast:可显式执行所有编译器可执行的隐式类型转换操作(但不能执行多态类的交叉转换);
  • dynamic_cast:用于多态对象间(从基类到派生类的向下转换,从一个基类到另一个基类的交叉转换)的类型转换;
  • reinterpret_cast:对目标的内存二进制位进行低层次的重新解释(类似老式C语言的显示类型转换);

dynamic_cast 引用类型转换失败时会抛出bad_cast异常;而指针转换失败时则返回空指针(nullptr);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值