右值引用与移动构造函数、移动赋值

本文详细解析了C++中右值引用的概念及其应用场景,并通过实例对比了左值引用与右值引用的区别。重点介绍了移动语义如何提高程序效率,包括移动构造函数与移动赋值操作的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  有一阵子没看C++了,翻开C++Primer又陌生了一些,想了想引用,于是乎来看了下右值引用。

int a=5;
int &b=a;

  这是左值引用,若我们这样修改:

int a=5;
int &b=a+1;

  编译器会报错:非常量引用的初始值必须为左值。也就是右边的a+1是常量,常量给非常量引用赋值就报错。
我们可以这样修改就不报错了:

int a=5;
const int &b=a+1;

  此时我们可以试一试:

    int a = 5;
    const int &b = a+1;
    a++;
    cout << b;

  发现b的结果为6,也就是a++并没有影响到b,也就是说b指向的地址并不是a,而是一个新的地址存放着a+1的值,但是由于b被const修饰,我们无法修改b的值。
  C++11新增了右值引用,那什么是右值引用呢?右值引用用&&表示,右值可以是常量,或者一些表达式以及返回值的函数,如上面的左值引用,需要修改为const才能够把常量给赋值过来,而右值引用就如下:

    int a=5;
    int &&b=a+1;
    a++;
    cout<<b;

  该代码和上面的那串代码结果一样,其中变量b的地址是存放着a+1值的地址,不同点就是我们可以自由的修改b的值,而不像左值引用那样被const给限制。

移动语义和右值引用

  那么问题就来了,右值引用有什么用呢?我们来看看在类中的使用吧:

class A {
private:
    static int count;
    char *str;
    int len;
public:
    A(int n = 0, char ch = ' ') :len(n) {
        str = new char[n];
        for (int i = 0; i < n; i++)
        {
            str[i] = ch;
        }
        count++;
        showdata();
        cout << "构造函数" << endl;
    }
    ~A()
    {
        cout << "析构函数 :" << (void*)str << endl;
        delete[]str;
        str = nullptr;
    }
    A(const A &copy)
    {
        len = copy.len;
        str = new char[len];
        for (int i = 0; i < len; i++)
        {
            str[i] = copy.str[i];
        }
        count++;
        showdata();
        cout << "拷贝构造函数" << endl;
    }
    A(A &&copy)
    {
        str = copy.str;
        len = copy.len;
        copy.str = nullptr;
        copy.len = 0;
        count++;
        showdata();
        cout << "移动构造函数" << endl;
    }
    A operator+(const A &test)
    {
        A temp(len + test.len);
        for (int i = 0; i < len; i++)
        {
            temp.str[i] = str[i];
        }
        for (int i = 0; i < test.len; i++)
        {
            temp.str[i + len] = test.str[i];
        }
        return temp;
    }
    A operator=(const A &test)
    {
        if (this == &test)
            return *this;
        delete str;
        len = test.len;
        str = new char[len];
        for (int i = 0; i < len; i++)
            str[i] = test.str[i];
        return *this;
    }
    void showdata()
    {
        cout << count << endl;
        cout << "Len: " << len << endl;
        cout << "address: " << (void *)str << endl;
    }
    void Aprint()
    {
        if (len == 0)
            cout << "NULL" << endl;
        else
            for (int i = 0; i < len; i++)
                cout << str[i];
        cout << endl;
    }
};
int A::count = 0;
int main()
{
    A a(3, 'x'), b(2, 'o');
    A c(a+b);
    A d = a;
    cout << "a:";
    a.Aprint();
    cout << "b:";
    b.Aprint();
    cout << "c";
    c.Aprint();
    cout << "d:";
    d.Aprint();
    return 0;
}

结果:
这里写图片描述
代码、结果分析:
  首先,数字1、2下面对应的是a与b对象生成,调用构造函数,然后到a+b调用operator+,然后返回一个临时对象temp(调用构造函数),然后就到c(temp),此时并不是调用拷贝构造函数,而是移动构造函数,然后我们把原来的a+b产生的临时对象的str成员置成nullptr,于是调用了析构函数,析构0000000的位置(因为我们置临时对象为空了),而我们的c就相当于窃取了temp的内容(改变了对象里面的str指针),所以最后c的str地址没变,还是01052598而内容变为temp的。

  那么,这和拷贝构造有什么区别呢?区别就是,若我们使用拷贝构造函数,我们将会重新生成临时对象,然后对临时的数据进行批量的复制,然后又清除,很费力,而使用移动构造我们可以直接让数据指向临时数据所指向的数据,而不用进行批量的复制操作。就例如我们上面的代码,若不使用移动构造函数,a+b产生的临时对象让c调用拷贝构造,然后批量复制数据,若数据越大,就越影响计算机性能。

移动赋值

  同样的,我们既然可以这样修改构造函数为移动构造函数,我们也可以修改operator=,通过判断是否是自我复制,若不是,则将我们类的指针成员指向另一个对象的指针成员,省去很多操作,代码如下:

A operator=(A &&test) //此时的参数不能为const,因为我们要修改test的成员
    {
        if (this == &test)
            return *this;
        delete str;
        len = test.len;
        str = test.str;
        test.str = nullptr;
        test.len = 0;
        cout << "调用移动赋值" << endl;
        return *this;
    }

  若我们要使用移动赋值,我们可以将要让赋的值不是左值,看起来像右值,然后调用移动赋值运算,我们可以使用static_cast<>将对象的类型强制转换为classtype &&,除此之外,C++11提供了一种方式,通过使用utility头文件,使用std::move()。
如下:

    A a(3, 'x'), b(2, 'o');
    //a=static_cast<A>(b);
    a=std::move(b);
    a.Aprint();
    b.Aprint();

结果:
这里写图片描述

  可以看见对象b的值被置为空,而a的str值为对象b的str值。

<think>好的,我现在要回答用户关于C++中引用是否会调用构造函数的问题。首先,我需要理解用户的问题核心。用户想知道在使用引用时,是否会触发构造函数的调用,尤其是移动构造函数或其他类型的构造函数。 根据提供的引用内容,特别是引用[3]和[4],引用主要用于实现移动语义,其中移动构造函数是一个关键部分。移动构造函数接受引用作为参数,允许资源的所有权转移,而不是深拷贝,这可以提高效率。例如,当使用std::move将左转换为时,会调用移动构造函数,如引用[2]中的示例代码所示,其中fun(std::move(x))将x转为,但构造函数是否被调用要看是否有对应的移动构造函数定义。 另外,引用[5]提到,当默认拷贝构造函数被删除时,可以定义移动构造函数来进行移动构造,这说明引用确实会调用移动构造函数,前提是存在这样的构造函数。如果类中没有移动构造函数,即使使用引用,也可能调用拷贝构造函数,尤其是在编译器无法自动生成移动构造函数的情况下。 需要注意的是,引用本身并不会直接导致构造函数的调用,而是根据上下文中的对象初始化或赋值操作来决定。例如,当使用引用初始化一个新对象时,如果有移动构造函数可用,就会调用它;否则,可能会回退到拷贝构造函数,或者编译错误,如引用[5]所述的情况。 总结来说,引用是否调用构造函数取决于是否有对应的移动构造函数,以及使用场景。如果存在移动构造函数,并且使用引用初始化对象时,就会调用移动构造函数,否则可能调用拷贝构造函数或导致错误。</think>在C++中,引用本身不会直接触发构造函数的调用,但**引用的使用场景**(如对象初始化、赋值或参数传递)可能会调用特定类型的构造函数。以下是具体分析: --- ### 1. **引用构造函数的关系** 引用(`&&`)主要**移动构造函数**和**移动赋值运算符**相关联。当用初始化新对象时,若存在移动构造函数,则会优先调用它: ```cpp class MyClass { public: MyClass() {} // 默认构造函数 MyClass(MyClass&& other) { // 移动构造函数 // 资源转移(非拷贝) } }; MyClass obj1; MyClass obj2 = std::move(obj1); // 调用移动构造函数 [^4] ``` 此处`std::move(obj1)`将左转换为,触发移动构造函数的调用。 --- ### 2. **引用不一定会调用构造函数** 引用的本质是**类型声明**,其行为取决于具体使用场景: - **绑定到临时对象**:直接引用现有对象,不触发构造函数。 ```cpp int&& r = 10; // 引用直接绑定到字面量,无构造函数调用 ``` - **作为函数参数**:仅影响重载决议,函数内部逻辑决定是否构造对象。 ```cpp void func(MyClass&& obj) { // 参数是已存在的对象,不触发构造 // 使用obj } ``` --- ### 3. **其他构造函数的交互** - **无移动构造函数时**:若未定义移动构造函数引用可能回退到**拷贝构造函数**(如果可用)[^5]。 - **强制调用移动构造**:通过`std::move`显式转换左,可触发移动构造[^2]。 --- ### 4. **关键区别:移动构造 vs 拷贝构造** | 特性 | 拷贝构造函数 | 移动构造函数 | |---------------------|---------------------|---------------------| | 参数类型 | `const T&` | `T&&` | | 资源处理 | 深拷贝 | 资源转移(浅拷贝) | | 性能影响 | 较高 | 较低 | | 触发条件 | 左初始化 | 初始化 | --- ### 5. **应用场景优化** 引用通过避免不必要的拷贝提升性能,例如: - 函数返回临时对象时触发移动语义[^4]。 - 容器操作(如`std::vector::push_back`)利用移动构造优化资源管理[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值