在 C++ 中,左值引用和右值引用是两种不同的引用类型,用于区分对象是左值还是右值。完美转发(Perfect Forwarding)是一种技术,用于在模板函数中保持参数的左值或右值属性,从而避免不必要的拷贝和移动操作。完美转发通常使用 std::forward 和 std::move 来实现。
右值引用:T&&,绑定到临时对象。
左值引用:T&,绑定到有名字的对象。
std::move 是一个类型转换函数,它将一个左值转换为右值引用,从而启用移动语义。具体来说,std::move 返回一个 T&& 类型的表达式,即使原来的表达式是一个左值。
class MyClass {
private:
std::vector<int> data;
public:
// 移动构造函数
MyClass(MyClass&& other) noexcept : data(std::move(other.data)) {
std::cout << "Move constructor" << std::endl;
}
};
详细解释
MyClass(MyClass&& other) noexcept : data(other.data) {
std::cout << "Move constructor" << std::endl;
}
在这种情况下,other.data 仍然是一个左值引用,因此 data 的初始化将调用 std::vector 的拷贝构造函数,而不是移动构造函数。这会导致不必要的资源复制,失去了移动语义的优势。(因为vector本身存在移动拷贝,如一些基本数据类型,其实都无需考虑整个)
但是:如果是指针其实std::move(other.data)这一步其实加不加move无所谓的。应该指针是地址,也不存在资源的复制。
需要明确的是,通过std::move将左值转为右值,从而调用移动拷贝或者移动赋值。在资源上是把对象的内部资源转移出去,而不需要进行一个资源复制的一个操作
如果整个类内持有持有一个指针数组,数据量比较多的情况,那调用移动赋值或者移动拷贝成本相对是小的
class MyClass {
public:
// 普通构造函数
MyClass(int size) : data(new int[size]), size(size) {
//深拷贝
}
// 移动构造函数
MyClass(MyClass&& other) noexcept : data(other.data), size(other.size) {
// 将其他对象的状态设置为已移动状态
other.data = nullptr;
other.size = 0;
}
// 移动赋值运算符
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) { // 防止自我赋值
delete[] data; // 释放现有资源
// 接管其他对象的资源
data = other.data;
size = other.size;
// 将其他对象置为已移动状态
other.data = nullptr;
other.size = 0;
}
return *this;
}
~MyClass() {
delete[] data;
}
private:
int* data; // 指向动态分配的数组
int size; // 数组大小
};
MyClass obj2(20);
MyClass obj3 = std::move(obj2); // 调用移动构造函数
MyClass obj4;
obj4 = std::move(obj3); //调用移动赋值
以上代码在调用移动构造和移动赋值的节省了资源复制的问题
注意,如果内部成员非指针情况如上面的vector,list这些容器,由于这些容器本身具备移动拷贝和移动赋值,所以在进行操作时前面加一个(std::move),常规类型无所谓
完美转发(Perfect Forwarding)是C++11引入的一项特性,它允许函数模板将参数原封不动地传递给另一个函数,包括参数的类型和值类别(lvalue 或 rvalue)。完美转发的主要应用场景包括:
泛型编程:在编写通用的库函数或模板时,你可能希望函数能够处理各种类型的参数,并且保持参数的原始类型和值类别不变。
转发构造函数:在继承层次结构中,子类需要将参数转发给基类的构造函数,同时保持参数的原始类型和值类别。
工厂模式:在工厂函数中,你可能需要将参数转发给实际对象的构造函数,以创建不同类型的对象。
优势
希望函数能够处理各种类型的参数,并且保持参数的原始类型和值类别不变,主要有以下几个原因:
#include
#include
#include // for std::forward
class Resource {
public:
Resource(size_t size) : data(new int[size]), size(size) {
std::cout << "Resource constructed with size " << size << "\n";
}
// 拷贝构造函数
Resource(const Resource& other) : size(other.size) {
data = new int[other.size];
std::copy(other.data, other.data + other.size, data);
std::cout << "Resource copy-constructed with size " << size << "\n";
}
// 移动构造函数
Resource(Resource&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
std::cout << "Resource move-constructed with size " << size << "\n";
}
// 拷贝赋值运算符
Resource& operator=(const Resource& other) {
if (this != &other) {
delete[] data;
data = new int[other.size];
std::copy(other.data, other.data + other.size, data);
size = other.size;
std::cout << "Resource copy-assigned with size " << size << "\n";
}
return *this;
}
// 移动赋值运算符
Resource& operator=(Resource&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
std::cout << "Resource move-assigned with size " << size << "\n";
}
return *this;
}
// 析构函数
~Resource() {
delete[] data;
std::cout << "Resource destructed with size " << size << "\n";
}
private:
int* data;
size_t size;
};
使用完美转发的函数模板
假设我们有一个函数模板 process,它需要将参数传递给另一个函数 use_resource。我们希望 process 能够处理各种类型的参数,并且保持参数的原始类型和值类别。
void use_resource(Resource& res) {
std::cout << "Using resource with size " << res.size << "\n";
}
void use_resource(Resource&& res) {
std::cout << "Using temporary resource with size " << res.size << "\n";
}
template <typename T>
void process(T&& res) {
use_resource(std::forward<T>(res));
}
测试代码
int main() {
Resource res1(1000000); // 创建一个大型资源对象
// 传递左值
process(res1); // 调用 use_resource(Resource& res)
// 传递右值
process(Resource(2000000)); // 调用 use_resource(Resource&& res)
return 0;
}
输出结果
Resource constructed with size 1000000
Using resource with size 1000000
Resource constructed with size 2000000
Resource move-constructed with size 2000000
Using temporary resource with size 2000000
Resource destructed with size 2000000
Resource destructed with size 1000000
传递左值:
process(res1) 调用 use_resource(Resource& res),因为 res1 是一个左值。
res1 的资源没有被复制或移动,只是被引用。
传递右值:
process(Resource(2000000)) 调用 use_resource(Resource&& res),因为 Resource(2000000) 是一个临时对象(右值)。
临时对象的资源被移动到 use_resource 函数内部的新对象中,避免了昂贵的拷贝操作。
通过使用完美转发,process 函数能够正确地将参数的值类别传递给 use_resource 函数,从而确保了正确的处理逻辑和性能优化。特别是对于大型对象或资源密集型对象,避免了不必要的拷贝操作,提高了程序的性能。