C++ push_back()左值和右值的区别是什么?

开发 前端
C++11标准对左值和右值做了更细致的划分:左值 (Lvalue):表示内存中的一个具体位置,可以取地址,通常是一个对象或者变量的引用。右值 (Rvalue):临时的、不可取地址的对象,通常是表达式的结果。

首先理解下左值和右值。

C++98/03标准中:左值是指有名字的对象,右值是指临时对象,但是我们提到的时候并不多。

C++11标准对左值和右值做了更细致的划分:

左值 (Lvalue):表示内存中的一个具体位置,可以取地址,通常是一个对象或者变量的引用。

右值 (Rvalue):临时的、不可取地址的对象,通常是表达式的结果。

将亡值(xvalue, expiring value):一种特殊的右值,表示将要销毁的对象(如std::move()返回的对象)

int&& r = std::move(x);  // std::move(x) 是将亡值

纯右值(prvalue, pure rvalue):临时值,表示一个临时的对象或常量,如字面量、函数返回值等。

int&& r = 10;       // 10 是纯右值(临时对象)

类左值:类左值是一个统称,涵盖了左值(lvalue)和将亡值(xvalue)。它们表示可以引用的对象。类左值包括了所有能够通过&或&&引用的值,无论是左值还是将亡值。

int x = 10;
int& r = x;  // x 是类左值(glvalue)
int&& r2 = std::move(x);  // std::move(x) 是将亡值(xvalue)

push_back()是std::vector容器的一个成员函数,用来将一个元素添加到容器的末尾。 

C++11之前,push_back()只有接受const左值引用的版本,所以无论是左值还是右值都会被拷贝到vector中。但C++11引入了移动语义,这时候有了右值引用的重载版本。

如果有std::string str = "hello"; 

然后v.push_back(str),这时候str是左值,会被拷贝。 

而如果是v.push_back(std::move(str)),或者直接push_back("hello"),这时候就是右值,触发移动构造,原str的内容被移动到vector中,之后str就空了。

左值传递: 

调用push_back(左值)时,会调用拷贝构造函数。 

#include <iostream>
#include <vector>

class MyClass {
public:
    MyClass(int value) : value(value) {
        std::cout << "MyClass(" << value << ") constructor\n";
    }

    // 拷贝构造函数
    MyClass(const MyClass& other) : value(other.value) {
        std::cout << "MyClass(" << value << ") copy constructor\n";
    }

private:
    int value;
};

int main() {
    std::vector<MyClass> vec;
    MyClass obj(10);
    // 左值传递
    vec.push_back(obj);  // 会调用拷贝构造函数
    return 0;
}

输出: 

MyClass(10) constructor
MyClass(10) copy constructor

在这个例子中,当我们将obj(一个左值)传递给push_back()时,std::vector需要复制这个对象,调用了MyClass的拷贝构造函数。 

右值传递: 

调用push_back(右值)时,会调用移动构造函数。 

容器可以“移动”这个对象到内部,而不需要进行复制。 

这意味着会调用对象的移动构造函数(如果存在的话),更高效,尤其是当对象比较大或包含大量数据时。 

#include <iostream>
#include <vector>

class MyClass {
public:
    MyClass(int value) : value(value) {
        std::cout << "MyClass(" << value << ") constructor\n";
    }

    // 移动构造函数
    MyClass(MyClass&& other) noexcept : value(other.value) {
        std::cout << "MyClass(" << value << ") move constructor\n";
    }

private:
    int value;
};

int main() {
    std::vector<MyClass> vec;

    // 右值传递
    vec.push_back(MyClass(20));  // 会调用移动构造函数
    return 0;
}

输出: 

MyClass(20) constructor
MyClass(20) move constructor

在这个例子中,通过MyClass(20)创建了一个临时对象,这是一个右值。当它传递给push_back()时,std::vector会调用移动构造函数,而不是拷贝构造函数。这样,MyClass(20)的资源会被“移动”到vec中,而不需要额外的内存分配和复制数据。 

底层实现原理: 

std::vector 的 push_back() 提供两个重载版本: 

void push_back(const T& val);  // 左值版本:拷贝
void push_back(T&& val);       // 右值版本:移动(C++11 新增)

vector的push_back有两个重载版本:

一个是const T&,另一个是T&&。

当传入左值时,编译器选择第一个版本,进行拷贝;

当传入右值时,选择第二个版本,进行移动。 

总结: 

特性

左值(push_back(a))

右值(push_back(std::move(a)))

拷贝/移动

拷贝构造

移动构造

原对象状态

保留原值

有效但未定义(通常为空)

性能

可能较慢(深拷贝)

通常更快(仅仅转移资源)

适用对象

需要保留的具名对象

临时对象或者不再需要的对象

 

责任编辑:武晓燕 来源: CppPlayer
相关推荐

2022-02-16 12:52:22

C++项目编译器

2022-03-11 07:59:09

容器代码元素

2022-09-19 15:57:36

JVM对象缓存

2024-04-30 09:02:48

2024-08-20 08:29:55

2024-10-10 16:53:53

守护线程编程

2024-01-01 08:25:53

ViewSurface框架

2010-02-03 17:32:54

C++左值与右值

2022-03-13 18:53:31

interfacetypeTypeScript

2023-06-01 08:15:04

CentOS红帽

2024-05-27 00:00:00

localhostIPv6IPv4

2020-08-11 11:00:16

左值引用右值引用移动语义

2024-09-02 00:30:41

Go语言场景

2012-02-13 10:18:42

C++ 11

2025-01-23 00:00:00

负数抽象泄漏机制

2023-12-29 22:41:12

同步架构业务

2021-04-11 11:20:26

数字人民币数字货币区块链

2022-07-26 00:36:06

C#C++函数

2021-02-06 21:57:40

Debug模式Release

2023-12-20 08:23:53

NIO组件非阻塞
点赞
收藏

51CTO技术栈公众号