C++11&QT复习 (十六)

发布于:2025-04-08 ⋅ 阅读:(15) ⋅ 点赞:(0)

Day11 移动语义回顾

一、移动语义基础概念

  • 移动语义:通过“转移资源所有权”而非“复制资源”,提升程序效率,尤其适用于临时对象或大对象的处理。
  • 移动构造函数移动赋值运算符:C++11引入的特殊成员函数,分别用于从右值中构造对象或赋值给现有对象。
  • 与拷贝语义的区别
    • 移动语义不会拷贝内存,只是指针赋值,效率更高。
    • 编译器不会自动生成移动构造和移动赋值,需要手动实现。

二、自定义 String 类的移动语义实现

class String
{
    friend std::ostream& operator<<(std::ostream& os, const String& rhs);

public:
    // 默认构造函数
    String()
        : _pstr(nullptr) {
        cout << "String()" << endl;
    }

    // 构造函数:深拷贝字符串
    String(const char* pstr)
        : _pstr(new char[strlen(pstr) + 1]()) {
        cout << "String(const char* pstr)" << endl;
        strcpy_s(_pstr, strlen(pstr) + 1, pstr);
    }

    // 拷贝构造函数:深拷贝另一个 String 对象
    String(const String& rhs)
        : _pstr(new char[strlen(rhs._pstr) + 1]()) {
        cout << "String(const String& rhs)" << endl;
        strcpy_s(_pstr, strlen(rhs._pstr) + 1, rhs._pstr);
    }

    // 移动构造函数:资源转移,不再复制字符串内容
    String(String&& rhs)
        : _pstr(rhs._pstr) {
        cout << "String(String&& rhs)" << endl;
        rhs._pstr = nullptr;  // 清空源对象
    }

    // 拷贝赋值运算符
    String& operator=(const String& rhs) {
        cout << "String& operator=(const String& rhs)" << endl;
        if (this != &rhs) //1、防止自复制
        {
            delete[] _pstr;//2、释放左操作数
            //3、深拷贝
            _pstr = new char[strlen(rhs._pstr) + 1]();
            strcpy_s(_pstr, strlen(rhs._pstr) + 1, rhs._pstr);
        }
        //4、返回*this
        return *this;
    }

    // 移动赋值运算符
    String& operator=(String&& rhs) {
        cout << "String& operator=(String&& rhs)" << endl;
        if (this != &rhs)//1、自移动
        {
            delete[] _pstr;//2、释放左操作数
            //3、浅拷贝
            _pstr = rhs._pstr;
            rhs._pstr = nullptr;
        }
        //返回*this
        return *this;
    }

    ~String() {
        cout << "~String()" << endl;
        delete[] _pstr;
        _pstr = nullptr;
    }

private:
    char* _pstr;
};
输出运算符重载:
std::ostream& operator<<(std::ostream& os, const String& rhs)
{
    if (rhs._pstr) {
        os << rhs._pstr;
    }
    return os;
}

三、测试函数:验证移动与拷贝行为

void test() {
    String s1("hello");
    cout << "s1 = " << s1 << endl;

    String s2 = s1;  // 拷贝构造
    cout << "s2 = " << s2 << endl;

    String s3 = "world";  // 隐式调用构造函数,然后移动构造
    //上面这条代码有个隐式转换的过程,"world" --> String("world")
	//String("world")是临时对象/匿名对象
    cout << "s3 = " << s3 << endl;

    String s4("hainan");
    cout << "s4 = " << s4 << endl;

    s4 = String("shangfa");  // 临时对象移动赋值
    cout << "s4 = " << s4 << endl;

    s4 = std::move(s4);  // 自移动,测试保护机制
    cout << "s4 = " << s4 << endl;//warning!使用已移动的 from 对象: s4 (lifetime.1)。
	//移动赋值运算符函数中没有判断if(this != &rhs)之前,
	// 打印s4的值,输出结果为空,因为在输出流重载运算符函数中,已经进行了判空操作,若指向为空,直接执行return 
	cout << "11111" << endl;

    /*std::move可以将左值转换为右值,实质上没有做任何移动,只是在底层做了强制转换static_cast<T &&>(lvalue)
	如果以后不想再使用某个左值,可以使用std::mobe将其转换为右值,以后就不再使用了*/
    s2 = std::move(s1);  // s1 被移动后已失效
    cout << "s1 = " << s1 << endl;
    cout << "s2 = " << s2 << endl;
}

四、左值与右值的补充说明

void testRightValue()
{
	int a = 10;
	int b = 20;
	int* pflag = &a;
	string s1("hello");
	string s2("world");

	int& ref = a;
	//int& ref2 = 10;//error!非常量引用的初始值必须为左值
	const int& ref2 = 10;
	/*const左值引用既可以绑定到左值,也可以绑定到右值*/
	const int& ref3 = a;

	&a;//左值
	&b;//左值
	&pflag;//左值
	& *pflag;//左值
	&s1;//左值
	&s2;//左值
	//表达式必须为左值或函数指示符
	//&(a + b);//左值
	//&(s1 + s2);//左值

	&ref;//左值

	//C++11之前是不能识别右值的,C++11之后新增语法可以识别右值
	int&& rref = 10;//右值引用
	//int&& rref2 = b;//error!无法将右值引用绑定到左值

	//还有一个问题,左值引用是左值还是右值?
	&rref;//右值引用在此处是左值(右值引用在作为函数参数的时候,体现出来的是左值的含义)

}

//那么,右值引用可以是右值吗?(右值引用作为函数返回类型的时候是右值)
int&& func()
{
	return 10;
}

int main()
{
	test();
	//&func();//error!“&”要求左值
	return 0;
}
右值引用作为函数返回值
int&& func() {
    return 10;
}

右值引用返回的对象仍然是右值,不能取地址。


五、知识总结

类型 是否可绑定左值 是否可绑定右值 备注
左值引用 (T&) 常用于一般变量引用
const 左值引用 (const T&) 可以绑定临时对象,常用于函数参数
右值引用 (T&&) 主要用于移动语义
如何区分左值与右值?
  • 能否取地址(&) 是判断左值的重要标准。
  • 表达式结果是否具名或可持久存在

六、附加说明:关于 std::move

  • std::move(x) 实质上是 static_cast<T&&>(x),并不真正“移动”。
  • 它只是告诉编译器:“我不再使用这个对象了,可以安全地‘偷走’资源”。
  • 右值的存放位置(冷知识):右值短暂的存在于栈上