您的当前位置:首页正文

C++右值引用问题解决

2024-11-06 来源:个人技术集锦

1、右值引用与函数重载

class Int
{
    int value;
public:
    Int(int x = 0) :value(x) { cout << "create " << this << endl; }
    ~Int() { cout << "destroy " << this << endl; }
    Int(const Int& it) :value(it.value)
    {
        cout << &it << " copy " << this << endl;
    }
    Int& operator=(const Int& it)
    {
        if (this != &it)
        {
            value = it.value;
        }
        cout << &it << " operator= " << this << endl;
        return *this;
    }
    void PrintValue()const
    {
        cout<<"value: "<<value<<endl;
    }
    Int& SetValue(int x)
    {
        value=x;
        return *this;
    }
};
//void func(Int a){}//会和void fun(Int&& c)形成二义性,原因是无名对象即可以赋值给对象也可以赋值给右值引用
void func(Int& a)
{
    cout<<"lvalue_reference"<<endl;
}
void func(const Int& b)
{
    cout<<"lvalue_const_reference"<<endl;
}
void func(Int&& c)
{
    cout<<"rvalue_reference"<<endl;
}
int main()
{
    Int x(10);
    const Int y(20);
    func(x);//优先匹配func(Int& a),没有就匹配func(const Int& b)
    func(y);//只能匹配func(const Int& b)
    func(Int(30));//优先匹配func(Int&& c),没有就匹配func(const Int& b)
    Int z(10);
    func((Int&&)z);//调用func(Int&& c)
    func((const Int&&)z);//调用func(const Int& b)
    return 0;
}

2、右值引用优化性能,避免深拷贝

class MyString
{
private:
    char* str;
public:
    MyString(const char* p=nullptr):str(nullptr)
    {
        if(p!=nullptr)
        {
            int n=strlen(p)+1;
            str=new char[n];
            strcpy_s(str,n,p);
        }
        cout<<"Create MyString"<<this<<endl;
    }
    ~MyString()
    {
        if(str!=nullptr)
        {
            delete[] str;
            str=nullptr;
        }
        str=nullptr;
    }
    MyString(const MyString& st):str(nullptr)//深拷贝
    {
        if(st.str!=nullptr)
        {
            int n=strlen(st.str)+1;
            str=new char[n];
            strcpy_s(str,n,st.str);
        }
        cout<<"Copy Create MyString"<<this<<endl;
    }
    MyString& operator=(const MyString& st)//深赋值
    {
        if(this==&st||str==st.str)
        {
            return *this;
        }
        delete[] str;//str为空时也可以进行delete,因为delete调用free,在free中会进行判空
        int n=strlen(st.str)+1;
        str=new char[n];
        strcpy_s(str,n,st.str);
        cout<<this<<"operator= MyString"<<&st<<endl;
        return *this;
    }
    void Print()const
    {
        if(str!=nullptr)
        {
            cout<<str<<endl;
        }
    }
};

2.1使用深拷贝和深复制会对堆区空间造成巨大影响

MyString func(const char* p)
{
    MyString tmp(p);
    return tmp;
}
int main()
{
    MyString s1("hello");
    s1=func("helloworld");
    s1.Print();
    return 0;
}

在main函数中能看见的字符串"hello"和"helloworld"均存放在.data区,p指针指向的也是.data区的"helloworld"。运行时进入主函数,首先创建s1对象,在堆区空间申请空间存放字符串"hello",s1.str指向该堆区空间。

再调用func()函数,进入func()函数先创建tmp对象,在堆区申请空间存放字符串"helloworld",tmp.str指向该堆区空间。

返回时,使用tmp对象构建将亡值对象xvalue,同样的,在堆区申请和tmp所申请大小相同的空间,将字符串"helloworld"赋值过去进行存放,xvalue.str指向该堆区空间。需要注意的是xvalue这个将亡值对象是在main栈帧中创建的,而不是func()函数栈帧中。创建完将亡值对象后,func()函数结束销毁tmp对象,将其所指向的堆区空间进行释放。

再回到主函数,将该将亡值对象赋值给s1对象时,调用赋值函数。首先申请和将亡值对象申请大小相同的空间,将字符串"helloworld"赋值过去进行存放,释放s1对象开始指向的堆区空间,之后再将s1.str重新指向新申请的空间。析构所有对象这样整个程序结束。

由此看来,深拷贝在一些情况下严重干扰堆空间,对其不停的申请和释放。

2.2使用移动拷贝构造和移动赋值提升性能(移动资源)

//移动拷贝构造
MyString(MyString&& st):str(nullptr)
{
    str=st.str;
    st.str=nullptr;
    cout<<"Move Copy Create"<<endl;
}
//移动赋值
MyString& operator=(MyString&& st)
{
    if(this==&st)return *this;
    delete[] str;
    str=st.str;
    st.str=nullptr;
    cout<<"Move Operator= "<<endl;
    return *this;
}

加入移动拷贝构造和移动赋值后再执行下面代码:

MyString func(const char* p)
{
    MyString tmp(p);
    return tmp;
}
int main()
{
    MyString s1("hello");
    s1=func("helloworld");
    s1.Print();
    return 0;
}

同样的,运行时进入主函数,首先创建s1对象,在堆区空间申请空间存放字符串"hello",s1.str指向该堆区空间。

再调用func()函数,进入func()函数先创建tmp对象,在堆区申请空间存放字符串"helloworld",tmp.str指向该堆区空间。

返回时tmp为左值对象,但系统认为在func函数中定义的局部对象tmp其生存期只在该函数中,使用return返回tmp对象时,认为是要将tmp的资源进行移动,就会将其看成是将亡值。(系统“作弊”)

所以在func函数返回前会调用移动拷贝构造函数将tmp对象的资源移动给创建的xvalue将亡值,func函数结束,析构tmp对象。

回到main函数时,将将亡值xvalue赋值给s1对象时,调用移动赋值函数,将xvalue的资源再次移动给s1对象,赋值结束后xvalue将亡值对象消亡,打印s1的内容,析构所有对象程序结束。

在这个过程中,并没有多次反复的申请空间和释放空间,而是将申请的空间在多个对象之间进行移动,这样就使得程序的性能提高。

如果将str设置为公有,即可在func和主函数中打印str的值,会发现tmp和func("helloworld")以及赋值完成后的s1的str值都相同,说明他们一直指向同一块内存空间。

您可能感兴趣的文章:
Top