C++11智能指针和引用计数
你有什么办法解决资源泄露(内存泄露)?
原理: 首先它是一个指针,对裸指针(带*号的指针)用面向对象的类进行封装,所以能指向内存资源;然后它区别于一般的指针,它是智能的,而所谓的智能是指它:
1.能够自动的帮你释放内存
2.还能够知道什么时候释放内存才是安全的。
C++中智能指针的实现主要依赖于两个技术概念:RAII技术
1. 析构函数:
对象被销毁时会被调用的一个函数,对于基于栈的对象而言,如果对象离开其作用域则对象会被自动销毁,而此时析构函数也自动会被调用。
引用计数这种计数是为了防止内存泄露而产生的。基本想法是对于动态分配的对象,进行引用计数,每当增加一次对同一个对象的引用,那么引用对象的引用计数就会增加一次,每删除一次引用,引用计数就会减一,当一个对象的引用计数减为零时,就自动删除指向的堆内存。
注意:
引用计数不是垃圾回收,引用技术能够尽快收回不再被使用的对象,同时在回收的过程中也不会造成长时间的等待,更能够清晰明确的表明资源的生命周期。不带引用计数的指针========================
auto_ptr
c++标准库,低版本VS没有加入到标准库。不建议使用该智能指针,C++11已结弃用该关键字。
拷贝构造的时候:先保存之前的指针,再把之前的指针置空,让新对象的指针指向堆,只让最后的指针指向堆资源。
注意:不能在容器中使用auto_ptr ,容器操作涉及许多拷贝构造,不允许浅拷贝。
我们知道auto_ptr通过复制构造或者通过=赋值后,原来的auto_ptr对象就报废了.所有权转移到新的对象中去了。这个过程可以进行,但是开发者需要自己注意原来的智能指针对象失效了,这个过程是对开发者隐藏的。
scoped_ptr
源码上将拷贝构造和赋值函数都设置为private了
不能拷贝构造,也不能赋值,这样就不用考虑浅拷贝的问题。
来源boost,低版本的C/C++标准库没有
1.怎么解决浅拷贝的?为什么可以用在容器中
一:可以间接作为容器元素
例如:
unique_ptr<int> sp(new int(88) );
vector<unique_ptr<int> > vec;
vec.push_back(std::move(sp));
//vec.push_back( sp ); 这样不行,会报错的.
//cout<<*sp<<endl;但这个也同样出错,说明sp添加到容器中之后,它自身报废了.
二:可以进行移动构造和移动赋值操作。
既然是独占,换句话说就是不可复制。但是,我们可以利用 std::move 将其转移给其他的 unique_ptr,
例如:
#include <iostream>
#include <memory>
struct Foo {
Foo() { std::cout << "Foo::Foo" << std::endl; }
~Foo() { std::cout << "Foo::~Foo" << std::endl; }
void foo() { std::cout << "Foo::foo" << std::endl; }
};
void f(const Foo &) {
std::cout << "f(const Foo&)" << std::endl;
}
int main() {
std::unique_ptr<Foo> p1(std::make_unique<Foo>());
// p1 不空, 输出
if (p1) p1->foo();
{
std::unique_ptr<Foo> p2(std::move(p1));
// p2 不空, 输出
f(*p2);
// p2 不空, 输出
if(p2) p2->foo();
// p1 为空, 无输出
if(p1) p1->foo();
p1 = std::move(p2);
// p2 为空, 无输出
if(p2) p2->foo();
std::cout << "p2 被销毁" << std::endl;
}
// p1 不空, 输出
if (p1) p1->foo();
// Foo 的实例会在离开作用域时被销毁
}
三:无法进行复制构造与赋值操作。
auto_ptr<int> ap(new int(88 );
auto_ptr<int> one (ap) ; // ok
auto_ptr<int> two = one; //ok
//但unique_ptr不支持上述操作
unique_ptr<int> ap(new int(88 );
unique_ptr<int> one (ap) ; // 会出错
unique_ptr<int> two = one; //会出错
2.为什么有两个模板参数,前面两个只有一个模板参数。
一个只能指针类型一个删除器仿函数/函数对象,智能指针的删除器,对资源的释放行为进行定制,比如说文件需要close()
3. 使用示例std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 从 C++14 引入
std::unique_ptr<int> pointer2 = pointer; // 非法
make_unique 并不复杂,C++11 没有提供 std::make_unique,可以自行实现:
template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args ) {
return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}
带引用计数的智能指针=====================
shared_ptr强智能指针
但还不够,因为使用 std::shared_ptr 仍然需要使用 new 来调用,这使得代码出现了某种程度上的不对称。于是在编码中尽量不使用new,使用make_shared,函数模板。
std::make_shared 就能够用来消除显示的使用 new,所以std::make_shared 会分配创建传入参数中的对象,并返回这个对象类型的std::shared_ptr指针。
#include <iostream>
#include <memory>
void foo(std::shared_ptr<int> i)
{
(*i)++;
}
int main()
{
// auto pointer = new int(10); // 非法, 不允许直接赋值
// 构造了一个 std::shared_ptr
auto pointer = std::make_shared<int>(10);
foo(pointer);
std::cout << *pointer << std::endl; // 11
// 离开作用域前,shared_ptr 会被析构,从而释放内存
return 0;
}
std::shared_ptr 可以通过 get() 方法来获取原始指针,通过 reset() 来减少一个引用计数,并通过get_count()来查看一个对象的引用计数。例如:
auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer; // 引用计数+1
auto pointer3 = pointer; // 引用计数+1
int *p = pointer.get(); // 这样不会增加引用计数
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3
pointer2.reset();
std::cout << "reset pointer2:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0, pointer2 已 reset
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
pointer3.reset();
std::cout << "reset pointer3:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 0, pointer3 已 reset
weak_ptr弱智能指针
weak_ptr只能观察对象存活与否,不会引起对象的资源引用计数的改变,相当于一个观察者模式。不会引起引用计数的改变。
智能指针的两个问题:
一:智能指针的交叉引用/循环引用问题
#include <iostream>
#include <memory>
class A;
class B;
class A {
public:
std::shared_ptr<B> pointer;
~A() {
std::cout << "A 被销毁" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> pointer;
~B() {
std::cout << "B 被销毁" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->pointer = b;
b->pointer = a;
return 0;
}
场景:
解决方法:引入弱智能指针weak_ptr
原因是:
弱智能指针只是一个观察者,不会改变引用计数。弱智能指针没有重载*和->运算符的重载方法,但是不能通过弱智能指针去访问对象。正确使用引用计数的智能指针的规则;
定义一个对象的时候用强智能指针去引用该对象。
在声明或者是类里面声明的时候用弱智能指针。
当想用成员里的弱智能指针去访问对象时,使用弱智能指针的lock方法转换返回一个强智能指针去访问对象成员。
正确代码:
#include <iostream>
#include <memory>
class A;
class B;
class A {
public:
// A 或 B 中至少有一个使用 weak_ptr
std::weak_ptr<B> pointer;
~A() {
std::cout << "A 被销毁" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> pointer;
~B() {
std::cout << "B 被销毁" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->pointer = b;
b->pointer = a;
return 0;
}
智能指针在多线程安全的问题!
