关于智能指针
template <class T>
class shared_ptr
{
public:
T& operator*() const
{ return *px; }
T* operator->() const
{ return px; }
shared_ptr(T* p) : px(p) { }
private:
T* px;
long* pn;
......
};
struct Foo
{
...
void method(void);
};
shared_ptr<Foo> sp(new Foo());
Foo f(*sp);
sp->method(); //相当于执行px->method()
关键的就是两个操作符的重载,operator*()和operator->()。对于*的重载返回的是指针所指对象的引用,返回值应该是引用类型,因为这个对指针解引用这个操作是可以作为左值的。对于->的重载有一个比较tricky的地方,就是比如说上例中的sp->method()相当于执行px->method(),但是sp->按理说返回的只是px,也就是调用sp的操作符重载将会消耗一个->,那为什么对于px还是会有一个->呢?我们可以这样理解:箭头符号比较特别,得到的东西会继续使用箭头符号作用上去,可以理解为语言设计的一个规则。
关于迭代器
template <class T>
struct __list_node{
void* prev;
void* next;
T data;
};
template <class T, class Ref, class Ptr>
struct __list_iterator{
typedef __list_iterator<T, Ref, Ptr> self;
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T>* link_type;
link_type node;
bool operator==(const self& x) const { return node == x.node; }
bool operator==(const self& x) const { return node == x.node; }
reference operator*() const { return (*node).data; }
pointer operator->() const { return &(operator*()); }
...
};
重要的是两个操作符的重载
std::shared_ptr和std::weak_ptr
通过指针保持对象共享,让多个std::shared_ptr引用对象占用同一个对象。当指针对象被另一个对象引用时,共享其所有权,每个指针对象有一个use_count,记录这个对象共享的指针对象个数。当最后的指针对象被销毁时,use_count抵达0,指针对象管理的指向被管理对象的指针也被delete。
#include<iostream>
#include <memory>
class Test
{
public:
Test(){
std::cout << "Test()" << std::endl;
}
~Test(){
std::cout << "~Test()" << std::endl;
}
};
int main(){
std::shared_ptr<Test> p1 = std::make_shared<Test>();
std::cout << "1 ref:" << p1.use_count() << std::endl;
{
std::shared_ptr<Test> p2 = p1;
std::cout << "2 ref:" << p1.use_count() << std::endl;
std::shared_ptr<Test> p3 = p1;
std::cout << "3 ref:" << p1.use_count() << std::endl;
}
std::cout << "4 ref:" << p1.use_count() << std::endl;
system("pause");
return 0;
}
std::make_shared封装了new操作符,实现了对象的实例化。
过程:执行std::shared_ptr< Test> p2 = p1和std::shared_ptr< Test> p3 = p1,引用对象各自加1,一次打印2和3,当大括号结束,p2和p3生命周期结束,计算器执行两次减1操作,p1引用计算重新回到1,当函数运行完之时,p1会被销毁,此时计算器是1,就会调用p1的析构函数delete之前用std:: make_shared创建的Test对象的指针。完成整个引用对象的管理。
问题:相互引用时对象释放异常
#include <iostream>
#include <memory>
class B;
class A{
public:
A(){
std::cout << "class A : constructor" << std::endl;
}
~A(){
std::cout << "class A : destructor" << std::endl;
}
void referB(std::shared_ptr<B> test_ptr) {
_B_Ptr = test_ptr;
}
private:
std::shared_ptr<B> _B_Ptr;
};
class B{
public:
B(){
std::cout << "class B : constructor" << std::endl;
}
~B() {
std::cout << "class B : destructor" << std::endl;
}
void referA(std::shared_ptr<A> test_ptr){
_A_Ptr = test_ptr;
}
std::shared_ptr<A> _A_Ptr;
};
int main()
{
// test
{
std::shared_ptr<A> ptr_a = std::make_shared<A>(); //A引用计算器为1
std::shared_ptr<B> ptr_b = std::make_shared<B>(); //B引用计算器为1
ptr_a->referB(ptr_b); // B引用计算器加1
ptr_b->referA(ptr_a); // A引用计算器加1
}
system("pause");
return 0;
}
结果是A和B都没有调用析构函数
分析上面代码可知,std::shared_ptr< A>和std::shared_ptr< B>在接收std::make_shared实例化对象时,各自的引用计算都为1,而当执行ptr_a->referB(ptr_b)时,再次使用std::shared_ptr< B>,因此此时B的引用加1,即B的引用计算变成2;同理A的引用计算也变成2。当main函数退出前ptr_a和ptr_b引用计算均为2,main函数退出后引用计算均为1,构成相互引用。也就是会产生这样的情况:ptr_a等待ptr_b释放自己,这样ptr_a才能去释放ptr_b。同理,ptr_b也等待ptr_a释放自己,这样ptr_b才能去释放ptr_a。
可见,相互引用导致了相互释放冲突的问题,最终导致内存泄露发生
weak_ptr
将A和B中的函数成员由shared_ptr改为weak_ptr
运行结果:
可以看到ptr_a和ptr_b在main退出前,引用计算均为1,也就是说在A和B中对std::weak_ptr的引用不会引起引用计算加1,ptr_a和ptr_b可以正常释放,不会引起内存泄露。
另一个例子
可见std::weak_ptr拥有弱引用特性,不拥有对象,只有等到调用lock()函数时才会有可能拥有对象,std::weak_ptr有以下特性:
- weak_ptr用于辅助shared_ptr的,只能指向shred_ptr对象,而不能直接指向实际对象本身,如
weak_ptr<Abase> ptr (new Abase()); // 编译器会报错
- 只有调用lock()创建std::shared_ptr时才会引用对象;
- 可以使用lock函数来获取其对应的shared_ptr对象(lock()会创建shared_ptr指针,在该指针的作用域范围内,会使引用次数+1,出作用域,引用次数-1
- std::weak_ptr不能操作对象,没有实现重载运算符
#include <iostream>
#include <memory>
using namespace std;
class base {
public:
base(string name) : mName(name) {
cout << "construct " << mName << endl;
}
virtual ~base() {
cout << "deconstruct " << mName << endl;
}
void print() {
cout << mName << " will print" << endl;
}
public:
string mName;
};
class Abase {
public:
Abase(string name) : mName(name) {
cout << "construct " << mName << endl;
}
virtual ~Abase() {
cout << "deconstruct " << mName << endl;
}
void print() {
cout << mName << " will print" << endl;
}
public:
string mName;
};
int main(int argc, char* argv[]) {
{
shared_ptr<Abase> A(new Abase("AAAAAAA"));
// 或者用make_shared来构造
// shared_ptr<Abase> A = maek_shared<Abase> ("AAAAAAA");
shared_ptr<base> B(new base("BBBBBBB"));
// 返回指向实际对象的普通指针,等于->
Abase* ptr = A.get();
ptr->print();
cout << B.use_count() << endl;//1
cout << A.use_count() << endl;//1
weak_ptr<Abase> weaka = A;
weak_ptr<base> weakb = B;
{
// 获取shared_ptr对象,这会使实际对象的引用次数+1,出作用域后引用计数-1
shared_ptr<Abase> weakaLock = weaka.lock();
weakaLock->print();
cout << A.use_count() << endl;
}
if (!weakb.expired()) {
// 将获取的shared_ptr对象赋值给临时变量,在这句话结束以后临时变量资源被回收,
// 因此引用计数不会+1
weakb.lock()->print();
cout << B.use_count() << endl;
}
// 将weakb指向的对象清空
weakb.reset();
if (weakb.expired()) {
cout << "weakb is invalid" << endl;
}
cout << B.use_count() << endl;//1
cout << A.use_count() << endl;//1
}
system("pause");
return 0;
}
unique_ptr
unique是装指针的容器,且拥有关联指针的唯一所有权。其内部没有引用计数use_count,当unique对象超出作用域时会自动析构,delete关联的堆内存上的对象。
规则
- 不能复制,必须用std::move
// 转移后 A_ptr2 == nullptr
std::unique_ptr<Abase> A_ptr1 = std::move(A_ptr2);
- 使用
if( ptr1 == nullptr)
判断对象是否为空,即是否关联原始指针 - 通过get()获取关联原始指针;通过reset()重置对象为空;通过release()返回获取关联指针,并释放关联指针所有权,此时unique_ptr对象为nullptr