c++ 左值和右值
在 C++ 中,右值(rvalue) 和 左值(lvalue) 是两个基础概念,它们主要用来区分表达式中的对象的“值类别”,即对象是“可以被赋值的”还是“仅用于计算的”。
左值和右值的区别
-
左值(lvalue):指向内存中的某个位置,可以获取其地址。左值通常是具有持久性的内存对象,它可以位于赋值语句的左边。通常,变量、数组元素、引用等都是左值。
示例:
-
int a = 10; // a 是左值,因为它表示一个持久的对象,可以被赋值
-
a = 20; // 可以给左值 a 赋值
-
右值(rvalue):指的是不存储在内存中的临时值,只用于表达式的计算结果,通常不能获取其地址。右值是“临时的”,它通常位于赋值语句的右边,像字面量或表达式的结果。
示例:
-
int a = 10; // 10 是右值,它是一个临时的字面量
-
int b = a + 5; // (a + 5) 的结果是右值,它是一个表达式的结果
右值的操作
右值的操作主要与如何处理临时对象、转移资源和优化性能有关,特别是在 C++11 引入右值引用和移动语义之后,右值的处理方式得到了极大的增强。
1. 右值引用(Rvalue Reference,T&&
)
-
定义:C++11 引入了一种新的引用类型,叫做右值引用,其语法是
T&&
。右值引用允许我们绑定右值,并对它们进行操作。通过右值引用,可以避免不必要的复制操作,从而实现更高效的代码。 -
主要用途:
- 移动语义(Move Semantics):右值引用用于实现移动构造函数和移动赋值运算符,从而转移资源的所有权,而不进行深拷贝。
- 完美转发(Perfect Forwarding):右值引用配合模板用于在泛型编程中转发参数,避免不必要的拷贝或移动。
示例:
-
class MyClass {
public:
MyClass(int size) : data(new int[size]) {} // 构造函数
~MyClass() { delete[] data; } // 析构函数// 移动构造函数:通过右值引用,转移资源所有权
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr; // 将资源的所有权转移后,清空原对象
}private:
int* data;
};
2. 右值和移动语义
-
移动语义通过右值引用来优化性能。在函数中,如果参数是右值引用(如
T&&
),则可以通过移动资源而非复制资源来避免性能损耗。移动语义在涉及到资源管理(如动态内存、文件句柄、网络连接)时尤为重要。示例:移动语义通过
std::move
来触发: -
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1); // v1 的数据被“移动”到 v2,v1 变为空
3. 右值的常见操作
-
移动构造函数和移动赋值运算符:通过右值引用来优化类的资源管理。使用右值引用避免不必要的深拷贝。
移动构造函数和移动赋值运算符的定义一般如下:
-
MyClass(MyClass&& other) noexcept; // 移动构造函数
-
MyClass& operator=(MyClass&& other) noexcept; // 移动赋值运算符
-
临时对象的优化:右值是临时对象,它们会在不再使用时销毁。在某些情况下,编译器会优化掉这些临时对象的创建和销毁(如返回值优化 (RVO) 和命名返回值优化 (NRVO))。
-
std::move
:将左值显式地转换为右值,以便触发移动语义而非复制。例如:
std::string str1 = "Hello";
std::string str2 = std::move(str1); // str1 被移动到 str2,避免拷贝
-
std::forward
:用于模板中的完美转发,保持右值或左值的属性,用于函数模板参数传递时的转发。
template<typename T>
void func(T&& arg) {
someFunction(std::forward<T>(arg)); // 保持原有的左值或右值属性
}
5、右值操作总结
- 右值引用(
T&&
):允许绑定右值,并可以通过它来进行资源转移。 std::move
:将左值显式地转换为右值,以便触发移动语义。- 移动构造函数和移动赋值运算符:利用右值引用来避免资源的复制,而直接转移资源。
- 完美转发:利用右值引用和
std::forward
实现高效的参数传递。
右值和右值引用在现代 C++ 中极大地提升了性能,特别是在处理大对象和复杂资源时,能够避免大量不必要的复制操作。
std::move 和swap
1、介绍
std::move
是将对象转换为右值引用,允许资源移动,减少资源的复制。移动后,源对象的状态通常是不确定的,意味着它的资源已经被转移。std::swap
是交换两个对象的内容,通过调用构造函数(移动或复制)来交换两个对象的内部资源。
2. std::move
-
功能:
std::move
并不移动对象本身,而是将对象转变为一个右值引用(rvalue reference)。这允许对象的资源被“移动”,而不是“复制”。 -
主要用途:
std::move
通常用于移动语义,以减少不必要的复制操作,尤其是在处理资源(如内存、文件句柄)时。通过移动语义,可以避免拷贝资源而直接转移所有权。 -
用法:通过
std::move
,你可以将对象变成右值,从而触发其移动构造函数或移动赋值运算符。 -
注意事项:
- 使用
std::move
后,源对象的状态通常是不确定的(但必须保持可析构的状态)。因此,除非明确重新赋值或初始化源对象,否则不要再使用它。 std::move
本身并不会移动数据,它只是将对象转换为右值引用。真正的移动是在移动构造函数或移动赋值运算符中发生的。-
注意事项:
- 使用
std::move
后,源对象的状态通常是不确定的(但必须保持可析构的状态)。因此,除非明确重新赋值或初始化源对象,否则不要再使用它。 std::move
本身并不会移动数据,它只是将对象转换为右值引用。真正的移动是在移动构造函数或移动赋值运算符中发生的。
3. std::swap
-
功能:
std::swap
是用于交换两个对象的内容。它不涉及右值引用,而是将两个对象的值互换。通常使用对象的复制构造函数或移动构造函数来交换其内部资源。 -
主要用途:
std::swap
用于简洁地交换两个对象的状态,尤其在某些算法中(如排序算法)被频繁使用。 -
用法:
std::swap
可以交换两个同类型对象的内容。 -
注意事项:
std::swap
对所有可移动或可复制的对象都可以使用,且性能依赖于对象的移动构造函数或赋值运算符的实现。- 在自定义类中,如果对象较大,最好提供一个高效的移动构造函数和移动赋值运算符以便优化
swap
的性能。
不可复制不可移动对象(单例模式开发)
C++11 引入了移动语义,但通过控制拷贝构造函数、拷贝赋值运算符、移动构造函数和移动赋值运算符的访问性,开发者可以禁用这些操作,从而防止对象的复制和移动。
1. 不可复制的对象
为了创建不可复制的对象,可以将拷贝构造函数和拷贝赋值运算符声明为 delete
或者将它们声明为 private
,这样在编译期就会阻止复制行为。
在这个例子中:
- 拷贝构造函数
NonCopyable(const NonCopyable&) = delete
被删除,因此不能通过拷贝构造另一个对象。 - 拷贝赋值运算符
NonCopyable& operator=(const NonCopyable&) = delete
被删除,因此不能通过赋值来复制对象。
class NonCopyable {
public:
NonCopyable() = default; // 默认构造函数
~NonCopyable() = default; // 默认析构函数
// 删除拷贝构造函数和拷贝赋值运算符
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
int main() {
NonCopyable obj1;
// NonCopyable obj2 = obj1; // 错误!拷贝构造函数被禁用
// NonCopyable obj3;
// obj3 = obj1; // 错误!拷贝赋值运算符被禁用
return 0;
}
2. 不可移动的对象
为了创建不可移动的对象,可以将移动构造函数和移动赋值运算符删除或声明为 private
。这样可以在编译期阻止移动操作。
在这个例子中:
- 移动构造函数
NonMovable(NonMovable&&) = delete
被删除,因此不能通过移动构造另一个对象。 - 移动赋值运算符
NonMovable& operator=(NonMovable&&) = delete
被删除,因此不能通过赋值进行移动。
class NonMovable {
public:
NonMovable() = default; // 默认构造函数
~NonMovable() = default; // 默认析构函数
// 删除移动构造函数和移动赋值运算符
NonMovable(NonMovable&&) = delete;
NonMovable& operator=(NonMovable&&) = delete;
};
int main() {
NonMovable obj1;
// NonMovable obj2 = std::move(obj1); // 错误!移动构造函数被禁用
// NonMovable obj3;
// obj3 = std::move(obj1); // 错误!移动赋值运算符被禁用
return 0;
}
3. 不可复制和不可移动的对象
为了创建既不可复制又不可移动的对象,可以同时删除拷贝构造函数、拷贝赋值运算符、移动构造函数和移动赋值运算符。这种设计常用于某些具有唯一性或需要保证单一实例的类。
在这个例子中,NonCopyableNonMovable
类既不能被复制也不能被移动,因为它的所有相关函数都被删除。
class NonCopyableNonMovable {
public:
NonCopyableNonMovable() = default;
~NonCopyableNonMovable() = default;
// 删除拷贝构造函数和拷贝赋值运算符
NonCopyableNonMovable(const NonCopyableNonMovable&) = delete;
NonCopyableNonMovable& operator=(const NonCopyableNonMovable&) = delete;
// 删除移动构造函数和移动赋值运算符
NonCopyableNonMovable(NonCopyableNonMovable&&) = delete;
NonCopyableNonMovable& operator=(NonCopyableNonMovable&&) = delete;
};
int main() {
NonCopyableNonMovable obj1;
// NonCopyableNonMovable obj2 = obj1; // 错误!拷贝构造函数被禁用
// NonCopyableNonMovable obj3 = std::move(obj1); // 错误!移动构造函数被禁用
return 0;
}
4. 实际应用场景
-
单例模式:在实现单例模式时,通常会禁用对象的复制和移动,以确保系统中始终只有一个实例。
-
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // 唯一实例
return instance;
}// 禁用复制和移动
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;private:
Singleton() = default; // 私有构造函数
};int main() {
Singleton& s1 = Singleton::getInstance();
// Singleton s2 = s1; // 错误,复制被禁用
// Singleton s3 = std::move(s1); // 错误,移动被禁用
return 0;
}
-
在单例模式中,复制和移动被禁用以保证系统中只有一个
Singleton
实例。 -
文件句柄、网络连接等系统资源管理类:为了防止资源不正确地被多次释放或移动到无效状态,一些管理系统资源的类(如文件句柄类)也通常禁止复制和移动操作。
5. 总结
- 不可复制的对象:通过删除拷贝构造函数和拷贝赋值运算符,可以防止对象的复制。
- 不可移动的对象:通过删除移动构造函数和移动赋值运算符,可以防止对象的移动。
- 不可复制和不可移动的对象:通过同时删除这些函数,可以完全阻止对象的复制和移动。
这些特性常用于单例模式、资源管理类等场景,以确保对象的唯一性和控制对象的生命周期。
原文地址:https://blog.csdn.net/weixin_43881088/article/details/143083674
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!