unique_ptr
的 引用捕获 vs 转移所有权我们知道unique_ptr
是c++的一种不可拷贝的类型,即以下操作是非法的:
std::unique_ptr<int> p1 = std::make_unique<int>(10);
std::unique_ptr<int> p2 = p1; // invalid, Call to implicitly-deleted copy constructor of 'unique_ptr'
std::unique_ptr<int> p3(p1); // invalid, Call to implicitly-deleted copy constructor of 'unique_ptr'
因此无法在lambda闭包
中直接按值捕获unique_ptr
:
std::unique_ptr<int> p1 = std::make_unique<int>(10);
auto closure = [p1] { // invalid, Call to implicitly-deleted copy constructor of 'unique_ptr'
printf("p1 = %d \n", *p1);
};
有2种方式可以在lambda闭包
中访问外部的unique_ptr
:引用捕获 和 转移所有权
引用捕获和转移所有权都可以使得unique_ptr
可以在lambda闭包
中使用,区别在于:
本质上是获得了外部作用域unique_ptr
变量的引用,其生命周期完全受外部作用域控制
std::unique_ptr<int> p1 = std::make_unique<int>(10);
auto closure = [&p1] {
printf("p1 = %d \n", *p1);
};
缺陷:unique_ptr
生命周期不受控,lambda闭包
内部访问时可能已经被外部修改或释放。如下面这种情况:
{
std::unique_ptr<int> ptr = std::make_unique<int>(10);
std::thread([&ptr]() {
printf("ohter thread ptr = %d \n", *ptr); // run time error: Thread 2: EXC_BAD_ACCESS (code=1, address=0x0)
}).detach();
printf("main thread ptr = %d \n", *ptr);
}
// console output:
// main thread ptr = 10
将外部作用域unique_ptr
的所有权转移到lambda闭包
内部,生命周期完全由lambda闭包
内部控制
std::unique_ptr<int> p1 = std::make_unique<int>(10);
auto closure = [p1 = std::move(p1)] {
printf("p1 = %d \n", *p1);
};
缺陷:转移之后,外部将无法再使用这个unique_ptr
。如下面这种情况:
{
std::unique_ptr<int> ptr = std::make_unique<int>(10);
std::thread([ptr = std::move(ptr)]() {
printf("ohter thread ptr = %d \n", *ptr);
}).detach();
printf("main thread ptr = %d \n", *ptr); // run time error: Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)
}
看个例子:
std::function<void()> func() {
auto ptr = std::make_unique<int>(1);
return [ptr = std::move(ptr)]() {
printf("*ptr = %d \n", *ptr);
};
}
编译错误:
include/c++/v1/__memory/compressed_pair.h:46:9: error: call to implicitly-deleted copy constructor of '(lambda at……
copy constructor of ‘’ is implicitly deleted because field ‘’ has a deleted copy constructor
return ptr = std::move(ptr) {
分析错误的原因,首先要先了解c++的lambda表达式
相关原理。
lambda表达式
事实上是定义了一个匿名类,并立即构建了一个该匿名类的实例对象。这个类内部重写了运算符operator()
,以便在使用的时候可以当成函数的方式去调用。
于是,上面例子中的代码可以写成等价于以下代码:
struct __anonymous {
__anonymous(std::unique_ptr<int> ptr) : _ptr(std::move(ptr)) {}
void operator()() {
printf("*ptr = %d \n", *_ptr);
}
private:
std::unique_ptr<int> _ptr;
};
std::function<void()> func() {
auto ptr = std::make_unique<int>(1);
return __anonymous(std::move(ptr));
}
当然,这段代码也有近乎类似的编译错误:
include/c++/v1/__memory/compressed_pair.h:46:9: error: call to implicitly-deleted copy constructor of ‘__anonymous’
copy constructor of ‘__anonymous’ is implicitly deleted because field ‘_ptr’ has a deleted copy constructor std::unique_ptr _ptr;
当类中包含有不可拷贝的成员变量的时候(如unique_ptr
),这个类就无法自动生成默认的拷贝函数(因为这个类里面包含有不可拷贝的成员变量)。因此当需要对这个类的对象进行拷贝操作的时候,如:
__anonymous a1;
__anonymous a2 = a1;
__anonymous a3(a1)
就无法正确定义这个类,因此产生编译错误。
⚠️⚠️⚠️如果实际使用的时候并没有上述拷贝操作,那么编译也不会有问题。主要是有触发拷贝类对象的时候有问题。
这里这个例子为什么会调用类的拷贝?
我们看到func()
方法返回的是一个std::function
对象,在构造std::function
对象的时候,这个lambda表达式
所构造的匿名的类对象,就会触发拷贝操作。而使用lambda闭包
来构造std::function
的时候,拷贝操作是无法避免的。
lambda表达式
,而非构造std::function
auto func() {
auto ptr = std::make_unique<int>(1);
return [ptr = std::move(ptr)]() {
printf("*ptr = %d \n", *ptr);
};
}
⚠️方法1并没有从根本上解决问题,只是避免了去构造std::function
,如果有强制使用std::function
的情况,错误仍然会出现
std::ref
把lambda表达式
包装成std::function
(只是解决了编译问题,运行仍然报错)std::function<void()> func() {
auto ptr = std::make_unique<int>(1);
auto lambda = [ptr = std::move(ptr)]() {
printf("*ptr = %d \n", *ptr);
};
return std::ref(lambda);
}
❌这里运行会报错,因为return之后,auto lambda
这个局部变量就会被释放掉,相对应的这个匿名类的内部被std::move
进来的这个std::unique_ptr
也会被释放掉,因此外部在调用这个std::function
的时候, std::unique_ptr
已经失效了
https://taylorconor.com/blog/noncopyable-lambdas/