掌握std::move和std::forward

本文介绍了C++中的左值和右值的概念,以及它们在表达式中的角色。接着讲解了左值引用和右值引用,特别是右值引用用于减少拷贝开销的功能。std::move函数可以将左值转换为右值,但并不实际移动数据。而std::forward则用于完美转发,保持原始参数的左值或右值性质,避免隐式转换带来的问题。文章通过实例展示了std::move和std::forward的使用效果。

在讲解std::move和std::forward之前,我们必须先了解C++中左值、右值的相关概念。

1、左值、右值

(1)左值:一般指的是在内存中有对应的存储单元的值,最常见的就是程序中创建的变量。
(2)右值:一般指的是没有对应存储单元的值(寄存器中的立即数,中间结果等),例如一个常量,或者表达式计算的临时变量。

判断某个表达式是左值还是右值的方法:

(1)可位于 = 左侧的表达式就是左值;反之,只能位于 = 右侧的表达式就是右值。

// 其中x是一个左值,字面值5是一个右值
int x = 5;

// 错误,5 不能为左值
5 = x;

说明:C++中的左值也可以当作右值使用。

// y 是一个左值
int y = 10;

// x、y 都是左值,但可以将 y 可以当做右值使用
x = y;

(2)有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。

上述示例中变量 x、y 是变量名且通过 &x 和 &y 可以获得他们的存储地址,因此 x 和 y 都是左值;反之,字面量 5、10,它们既没有名称,也无法获取其存储地址,因此 5、10 都是右值。

2、左值引用、右值引用

(1)左值引用:C++中采用&对变量进行引用,我们平时熟悉的引用就是左值引用。

    int num = 10;
    int& b = num; //正确
    int& c = 10; //错误

    const int& c = 10; //正确

(2)右值引用:

因为右值本身也没有对应的存储单元,所以没法进行引用。右值引用实际上只是一个逻辑上的概念,最大的作用就是让一个左值达到类似右值的效果(下面程序举例),让变量之间的转移更符合“语义上的转移”,以减少转移之间多次拷贝的开销。右值引用符号是&&。

注意:
(1)右值引用不支持引用左值;
(2)非常量右值引用可以引用的值的类型只有非常量右值;
(3)常量右值可以引用常量右值、非常量右值。

int num1 = 10;
const int num2 = 100;

int&& a = num1;	//编译失败,非常量右值引用不支持引用非常量左值
int&& b = num2;	//编译失败,非常量右值引用不支持引用常量左值
int&& c = 10;	//编译成功,非常量右值引用支持引用非常量右值

const int&& d = num1;	//编译失败,常量右值引用不支持引用非常量左值
const int&& e = num2;	//编译失败,常量右值引用不支持引用常量左值
const int&& f = 100;	//编译成功,常量右值引用支持引用右值

3、move()函数

move()函数将左值强制转换成右值。但move()并没有移动能力!

int num = 10;
int&& a = std::move(num);  //编译成功
std::cout << a << std::endl;   //输出结果为10;
std::cout << num << std::endl; //输出结果为10;move只是把左值强制转换成右值,并没有移动能力。

a = 20;
std::cout << num << std::endl; //输出20

为了加深对move()的印象,我们再来看一个例子:

std::string str1 = "I love C++";
std::string str2 = std::move(str1);
std::cout << "str2:" << str2 << std::endl;
std::cout << "str1:" << str1 << std::endl;

执行结果:

我们看到执行move()函数后,str1里的内容没了,难道被移走了?我们前面说过move()没有移动能力,造成str1的内容为空的原因是string的“移动构造函数”。 我们再来看一个例子:

std::string str3 = "I love C";
std::string&& def = std::move(str3);
std::cout << "str3:" << str3 << std::endl;
std::cout << "def:" << def << std::endl;

执行结果:

4、forward()函数

std::forward的作用是完美转发,如果传递的是左值转发的就是左值引用,传递的是右值转发的就是右值引用。

std::move可以减少不必要的拷贝开销,可以提高程序的效率。但是std::forward的作用是转发,左值引用转发成左值引用,右值引用还是右值引用,它的意义到底是什么?

原来是在程序的执行过程中,对于引用的传递实际上会有额外的隐式的转化,一个右值引用参数经过函数的调用转发可能会转化成左值引用,但这就不是我们希望看到的结果。

举例:

#include <iostream>

template<typename T>
void print(T& t) {
    std::cout << "左值" << std::endl;
}

template<typename T>
void print(T&& t) {
    std::cout << "右值" << std::endl;
}

template<typename T>
void testForward(T&& v) {
    print(v);
    print(std::forward<T>(v));
    print(std::move(v));
}

int main(int argc, char* argv[])
{
    testForward(1); // 传入右值

    std::cout << "======================" << std::endl;

    int x = 1;
    testForward(x); // 传入左值
}

执行结果:

 

参考:

(1)[C++特性]对std::move和std::forward的理解

(2)【C++】左值和右值、左值引用(&)和右值引用(&&)

(3)抄袭李超老师的std::forward()

C++中,`std::move` `std::forward` 是两个与模板类型推导引用折叠规则密切相关的工具,它们主要用于优化资源管理泛型编程。 ### `std::move` 的用途 `std::move` 主要用于将一个对象转换为引用,从而允许资源的移动而非复制。这在处理大型对象时尤其有用,因为它可以避免不必要的深拷贝操作,提高性能。例如,在使用 `std::shared_ptr` 时,可以通过 `std::make_shared` 来创建一个智能指针,这样可以减少内存分配次数并提高效率[^1]。 ```cpp std::shared_ptr<int> p3 = std::make_shared<int>(10); ``` ### `std::forward` 的用途 `std::forward` 用于完美转发,即在模板函数中将参数以原始类型的方式传递给另一个函数。这对于保持参数的特性非常重要,特别是在实现通用库函数时。完美转发通常与万能引用(universal references)一起使用,通过模板类型推导来保持参数的原始特性。 ```cpp template<typename T> void forward_example(T&& arg) { some_function(std::forward<T>(arg)); } ``` ### `std::move` 与 `std::forward` 的区别 - **用途不同**:`std::move` 主要用于移动语义,而 `std::forward` 用于完美转发。 - **作用对象不同**:`std::move` 通常作用于具名变量,将其转换为引用;而 `std::forward` 通常用于模板参数,保持其特性。 - **实现机制不同**:`std::move` 实际上是静态_cast到T&&的简写,而 `std::forward` 则根据模板参数的类型来决定是进行引用还是引用。 ### 示例代码 以下是一个简单的例子,展示了 `std::move` `std::forward` 的使用: ```cpp #include <iostream> #include <utility> #include <vector> class MyClass { public: MyClass() { std::cout << "Constructor\n"; } MyClass(const MyClass&) { std::cout << "Copy Constructor\n"; } MyClass(MyClass&&) noexcept { std::cout << "Move Constructor\n"; } }; void useMyClass(MyClass mc) { // Do something with mc } int main() { MyClass mc; // Using std::move to invoke move constructor MyClass mc2 = std::move(mc); // Should print "Move Constructor" // Using std::forward in a generic context std::vector<MyClass> vec; MyClass mc3; vec.push_back(std::forward<MyClass>(mc3)); // Should print "Copy Constructor" return 0; } ``` 在这个例子中,`std::move` 被用来显式调用移动构造函数,而 `std::forward` 在 `push_back` 操作中被用来保持 `mc3` 的特性。 ### 总结 `std::move` `std::forward` 都是C++11引入的重要特性,它们分别解决了资源移动完美转发的问题。理解它们的区别应用场景对于编写高效、现代的C++代码至关重要。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值