std::move失效问题

一、问题

在C++中,std::move是一个用于将对象转移所有权的函数模板,它将对象的状态从一个对象转移到另一个对象,通常用于优化移动语义。那std::move什么情况下会失效呢?

有这样一个代码需求:在lambda中,将一个捕获参数move给另外一个变量。看似一个很简单常规的操作,然而这个move动作却没有生效。

具体代码如下:

#include <iostream>
#include <vector>
using namespace std;

int main()
{
	std::vector<int> vec = {1,2,3};

	auto func = [=](){
		auto vec2 = std::move(vec);
		std::cout << vec.size() << std::endl; // 输出:3
		std::cout << vec2.size() << std::endl; // 输出:3
	};
	func();
   return 0;
}

 

我们期望的是,将对变量vec调用std::move后,数据将会移动至变量vec2, 此时vec里面应该没有数据了。但是通过打印vec.size()发现vec中的数据并没有按预期移走。

这也就意味着,构造vec2时并没有按预期调用移动构造函数,而是调用了拷贝构造函数。

二、std::move的本质

对于std::move,有两点需要注意:

  1. std::move中到底做了什么事情
  2. std::move是否可以保证数据一定能移动成功

对于第二点来说,答案显然是不能。这也是本文的问题所在。那么std::move实际上是做了什么事情呢?

对于std::move,其实现大致如下:

template <typename T>
constexpr std::remove_reference_t<T>&& move(T&& arg) noexcept {
    return static_cast<std::remove_reference_t<T>&&>(arg);
}

从代码可以看出,std::move本质上是调用了static_cast做了一层强制转换,强制转换的目标类型是remove_reference_t<T>&&,remove_reference_t是为了去除类型本身的引用,例如左值引用。总结来说,std::move本质上是将对象强制转换为了右值引用。

那么,为什么我们通常使用std::move实现移动语义,可以将一个对象的数据移给另外一个对象?

这是因为std::move配合了移动构造函数使用,本质上是移动构造函数起了作用。移动构造函数的一般定义如下:

class A{
public:
    A(A &&);
};

可以看到移动构造函数的参数就是个右值引用A&&,因此 A a = std::move(b);, 本质上是先将b强制转化了右值引用A&&,

然后触发了移动构造函数,在移动构造函数中,完成了对象b的数据到对象a的移动。

那么,在哪些情况下,A a = std::move(b);会失效呢?

显然是,当std::move强转后的类型不是A&&,这样就不会命中移动构造函数。

例如:

const std::string str = "123"
std::string str2(std::move(str));

这个时候,对str对象调用std::move,强转出来的类型将会是const string&&, 这样移动构造函数就不会起作用了,但是这个类型却可以令复制构造函数生效。

结合本文最初的问题,在lambda中move没有生效,显然也是std::move强转的类型不是std::vector<int>&&, 才导致了没有move成功。

那么,为什么会出现这个问题呢,我们需要理解下lambda的工作原理。

三、lambda闭包原理

对于c++的lambda,编译器会将lambda转化为一个独一无二的闭包类。而lambda对象最终会转化成这个闭包类的对象。

对于本文最初的这个lambda来说,最终实际上转化成了这么一个类型

// 转换前
auto func = [=](){
    auto vec2 = std::move(vec);
};

// 转换后
class ClosureFunc{
public:
    void operator() const{
        auto vec2 = std::move(vec);
    };

private:
    std::vector<int> vec;
};

ClosureFunc func;

这里需要注意, lambda的默认行为是,生成的闭包类的**operator()**默认被const修饰

那么这里问题就来了,当调用operator()时, 该闭包类所有的成员变量也是被const修饰的,此时对成员变量调用std::move

将会引发上文提到的,强转出来的类型将会是**const string&&**,同时,移动构造函数将不会被匹配到

我们最初的问题lambda中std::move失效的问题,也是因为这个原因。但这个也很符合const函数的语义: const函数是不能修改成员变量的值。

四、解决方案

那么,这个应该怎么解决呢?答案是mutable。即在lambda尾部声明一个mutable,如下:

auto func = [=]() mutable{
    auto vec2 = std::move(vec);
};

这样编译器生成的闭包类的operator()将会不带const了。我们的std::move也可以正常转换,实现移动语义了。

std::vector<int> vec = {1,2,3};
    
auto func = [=](){
    auto vec2 = std::move(vec);
    std::cout << vec.size() << std::endl; // 输出:0
    std::cout << vec2.size() << std::endl; // 输出:3
};

参考:

c++ lambda内std::move失效问题的思考-腾讯云开发者社区-腾讯云

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
3); pq.push(1); pq.push(2); while (!pq.empty()) { std::cout << pq.top() << " "; 好的,以下是用 JavaScript 实现冒泡排序的代码: ```javascript function bubbleSort(array) { const len = array pq.pop(); } ``` 26. 不要在迭代器失效的情况下修改容器 在使用 STL 容.length; for (let i = 0; i < len; i++) { for (let j = 0; j <器时,应该注意容器的迭代器是否会因为修改容器而失效。如果迭代器失效 len - 1 - i; j++) { if (array[j] > array[j + 1]) { [array[j],,则可能会导致未定义的行为。 27. 不要在使用 std::vector、std::string 或 std::deque array[j + 1]] = [array[j + 1], array[j]]; } } } return array; } ``时使用迭代器的比较运算符 在使用 std::vector、std::string 或 std::deque 时,应` 这里的 `bubbleSort` 函数接收一个数组作为参数,然后使用双重循环遍历数组,每该避免使用迭代器的比较运算符,因为这可能会导致未定义的行为。 28.次比较相邻的两个元素,如果前一个元素比后一个元素大,则交换它们的位置 在使用 std::unique_ptr 时,使用 std::move 将所有权转移 在使用 std::unique_ptr 时,应。经过一轮比较后,最大的元素会被移动到数组的最后面。然后再进行下一轮比较,不断重复这个过程,直到所有元素都被排好序为止。 使用使用 std::move 函数将所有权转移。这可以避免不必要的拷贝和析构开销。例如示例: ```javascript const arr = [3, 1, 4, 1, 5, 9, 2: ``` std::unique_ptr<int> p1(new int(1)); std::unique_ptr<int> p2(std::move(p1, 6, 5, 3, 5]; const sortedArr = bubbleSort(arr); console.log(sortedArr); // 输出 [)); ``` 29. 使用 std::function 来存储函数对象 STL 提供了 std::function 类型,可以方便1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9] ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值