智能指针
weak_ptr
- 一般配合shared_ptr使用,两者之间可以相互转换:
- shared_ptr转成weak_ptr:可以直接赋值,不会增加引用计数,weak_ptr有自己的引用计数:副引用计数
auto sp = std::make_shared<int>(42);
std::weak_ptr<int> gw = sp;
- weak_ptr转成shared_ptr,用以测试shared_ptr指向的对象是否过期,该操作是原子的:
std::shared_ptr<Widget> spw1 = wpw.lock(); //如果wpw已经过期 //spw1的值是null
auto spw2 = wpw.lock(); //结果同上,这里使用了auto
eg1:
#include <iostream>
#include <memory>
std::weak_ptr<int> gw;
void observe()
{
std::cout << "use_count == " << gw.use_count() << ": ";
if (auto spt = gw.lock()) { // Has to be copied into a shared_ptr before usage
std::cout << *spt << "\n";
}
else {
std::cout << "gw is expired\n";
}
}
int main()
{
{
auto sp = std::make_shared<int>(42);
gw = sp;
observe();
}
observe();
}
result:
eg2:
另外一种方式是以 std::weak_ptr 为参数,使用 std::shared_ptr 构造函数。这种情况下,如果 std::weak_ptr 指向的对象过期的话,会有异常抛出:
//如果wpw过期的话,抛出std::bad_weak_ptr异常
std::shared_ptr<Widget> spw3(wpw);
- 当两个对象存在相互引用的时候,推荐使用weak_ptr,不然会出现内存泄漏。 这是因为weak_ptr不会影响到引用计数;
eg:
class ClassB;
class ClassA
{
public:
ClassA() { cout << "ClassA Constructor..." << endl; }
~ClassA() { cout << "ClassA Destructor..." << endl; }
weak_ptr<ClassB> pb; // 在A中引用B
};
class ClassB
{
public:
ClassB() { cout << "ClassB Constructor..." << endl; }
~ClassB() { cout << "ClassB Destructor..." << endl; }
weak_ptr<ClassA> pa; // 在B中引用A
};
int main() {
shared_ptr<ClassA> spa = make_shared<ClassA>();
shared_ptr<ClassB> spb = make_shared<ClassB>();
spa->pb = spb;
spb->pa = spa;
// 函数结束,思考一下:spa和spb会正确释放资源
}
weak_ptr循环引用的资源可以得到正常释放,因为weak_ptr不会增加引用计数,如果使用shared_ptr的话,会导致生命周期结束时,引用计数无法减到0,从而导致内存泄漏;
unique_ptr
std::unique_ptr 是一个move-only类型,不能拷贝(拷贝构造和赋值运算符函数是被delete掉的),独占资源;
eg1:不支持拷贝和赋值:
unique_ptr<int> up1(new int()); // okay,直接初始化
unique_ptr<int> up2 = new int(); // 编译error! 构造函数是explicit(不允许隐式转换)
unique_ptr<int> up3(up1); // 编译error! 不允许拷贝
那么如何传递unique_ptr呢?
- 通过引用传递,引用传递需要注意的是对象的生命周期,引用传递不会改变引用计数;
- 移动构造
- 通过release转移所有权:调用release 会切断unique_ptr 和它原来管理的对象的联系。release 返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。如果不用另一个智能指针或者原始指针来保存release返回的指针,就会造成对象丢失,内存泄漏。
// 传入参数
// 传unique_ptr参数可以使用引用避免所有权的转移
// 这里传入的引用,并非拷贝
void func1(unique_ptr<int> &up)
{
cout<<*up<<endl;
}
//使用up作为参数
unique_ptr<int> up(new int(10));
//传引用,不涉及所有权的转移
func1(up);
unique_ptr<int> func2(unique_ptr<int> up)
{
cout<<*up<<endl;
return up; // 转移所有权
}
//暂时转移所有权,函数结束时重新收回所有权
//如果不用up重新接受func2的返回值,这块内存就泄漏了
up = func2(unique_ptr<int> (up.release()));
// 或者使用移动构造
up = func2(std::move(up));
eg3: 转移所有权
up.release()
up放弃对它所指对象的控制权,并返回保存的指针,将up置为空,不会释放内存
up.reset(…)
参数可以为空,返回值是void。先将up所指对象释放,然后重置up所指对象为参数值.会释放内存
// 将所有权从p1转移给p2
unique_ptr<string> p2(p1.release()); // release将p1置为空
unique_ptr<string> p3(new string("C++"));
// 将所有权从p3转移给p2
p2.reset(p3.release()); // reset释放了p2原来指向的内存
注: 不要与裸指针混用
unique_ptr不允许两个独占指针指向同一个对象,在没有裸指针的情况下,我们只能用release获取内存的地址,同时放弃对对象的所有权,这样就有效避免了多个独占指针同时指向一个对象。 而使用裸指针就很容器打破这一点
eg4:
int *x(new int());
unique_ptr<int> up1,up2;
//会使up1 up2指向同一个内存
up1.reset(x);
up2.reset(x);
闭包
闭包:与函数A调用函数B相比较,闭包中函数A调用函数B,可以不通过函数A给函数B传递函数参数,而使函数B可以访问函数A的上下文环境才可见(函数A可直接访问到)的变量;比如:
函数B(void) {
…
}
函数A {
int a = 10;
B(); //普通调用函数B
}
函数B无法访问a;但如果是按闭包的方式,则可以访问变量a:
函数A() {
int a = 10;
auto closure_B = a {
…
}
}
所以闭包的优缺点很清晰,都是同一个:可以不通过传参获取调用者的上下文环境;
在c++中,可以通过lambda表达式和std::bind实现闭包
lambda表示式
具有如下形式:
[捕获列表](参数)-> 返回类型 {函数体}
关于捕获列表:捕获调用者上下文环境的需要访问的变量,可按值捕获或按引用捕获,其规则如下:
[]:什么也不捕获
[=]:捕获所有一切变量,都按值捕获
[&]:捕获所有一切变量,都按引用捕获
[=, &a]:捕获所有一切变量,除了a按引用捕获,其余都按值捕获
[&, =a]:捕获所有一切变量,除了a按值捕获,其余都按引用捕获
[a]:只按值捕获a
[&a]:只按引用捕获a
[a, &b]:按值捕获a,按引用捕获b
关于参数列表:
和函数调用的参数列表含义完全一样;
注:
- 可以直接使用局部static变量和在它所在的函数之外声明的名字;
- mutable:必须注意加入mutable可以修改的是:在lambda匿名函数体里边,按值捕获到的变量,实质上是调用者函数中变量的只读拷贝(read-only),加入了mutable后,匿名函数体内部可以修改这个拷贝的值,也就是说调用者函数中该变量的值依然不会被改变
eg1:
int v1 =42;
// f可以改变它所捕获的变量
auto f=[v1]() mutable {return ++v1;};
v1 = 0;
auto j = f(); // j = 43
- 如果一个lambda体包含了return之外的任何语句,则编译器默认推断此lambda返回void。与其他返回void的函数类似,被推断返回void的lambda不能返回值。如果需要返回其他值,则需要显式指定返回类型:
eg2:
// 错误,不能推断lambda的返回类型
transform(vi.begin(), vi.end(), vi.begin(), [](int i){
if (i < 0)
return -i;
else return i;
});
// 正确写法
transform(vi.begin(), vi.end(), vi.begin(), [](int i) -> int {
if (i < 0)
return -i;
else return i;
});
function与bind
- function是一个class template,可调用对象,通过模块参数,指定对象的调用形式:
int add(int i, int j) {return i + j}
// 函数对象类
struct divide {
int operator() (int i, int j) {
return i / j;
}
}
function<int (int, int)> f1 = add; // 函数指针
function<int (int, int)> f2 = divide(); // 函数对象类的对象
function<int (int, int)> f3 = [](int i, int j) {return i * j;}; // lambda表达式
- bind是一个标准库函数function template,通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原调用对象的参数列表。其一般形式为:
auto newCallable = bind(callable, arg_list)
注:arg_list为参数列表,是传递给callable的参数列表,可能会包含_n的参数名(n是一个整数),这些参数被称为占位符,表示newCallable的参数,他们占据了newCallable的参数的“位置”,数值n表示生成的可调用对象的参数位置:_1为newCallable的第一个参数,_2为第二个参数
eg:
bool check_size(const string &s, string::size_type sz) {
return s.size() >= sz;
}
// 新的可调用对象 bind普通函数
auto check6 = bind(check_size, std::placeholders::_1, 6);
// 绑定成员函数
#include <iostream>
#include <functional>
class MyClass
{
public:
MyClass();
~MyClass();
void func1(int a, int b, int &sum) {
sum = a + b;
std::cout << "int func 1 " << sum << std::endl;
}
void func2(int a, int b){
int sum = 0;
// 成员函数的第一个参数为this指针
auto f = std::bind(&MyClass::func1, this, a, b, sum);
f();
}
};
MyClass::MyClass()
{
}
MyClass::~MyClass()
{
}
int main() {
MyClass test;
test.func2(1, 32);
system("pause");
}
// 运行结果如下
int func 1 33
请按任意键继续. . .