C++智能指针
shared_ptr 智能指针
std::shared_ptr采用引用计数,每一个shared_ptr的拷贝都指向相同的内容,当最后一个shared_ptr析构的时候,内存被释放
首先定义一个Person类
class Person
{
public:
static int index;
Person()
{
idx = Person::index++;
printf("create #%d person object\n", idx);
}
~Person()
{
printf("destory #%d person object\n", idx);
}
void say()
{
printf("I'm #%d person, my name is %s\n", idx, name.c_str());
}
void set_name(const std::string &s)
{
name = s;
}
private:
int idx;
string name;
};
int Person::index = 0;
创建
创建share_ptr有四种方法,分别为new、=、reset、make_shared
- new操作
new操作将一个原始指正转换成一个智能指针,转换后原始指针和指针指针指向同一块地址
int main()
{
Person *person = new Person();
std::shared_ptr<Person> ptr(person);
person->say();
ptr->say();
person->set_name("zhangsan");
person->say();
ptr->say();
ptr->set_name("lisi");
ptr->say();
person->say();
cout << (person == ptr.get()) << endl;
}
上述代码输出:
create #0 person object
I'm #0 person, my name is
I'm #0 person, my name is
I'm #0 person, my name is zhangsan
I'm #0 person, my name is zhangsan
I'm #0 person, my name is lisi
I'm #0 person, my name is lisi
1
destory #0 person object
对share_ptr和原始指针的操作都会互相影响,可以认为操作的是同一块地址,person == ptr.get()
输出了1。
注:
-
ptr.get()返回的是const指针,也就是说只能访问,不能修改。
-
切记不要用一个new出来的原始指针去创建多个share_ptr指针,这样一来同一块地址被不同的智能指针引用,但是这些智能指针没有公用一个计数器,这就导致该内存会被释放多次。
-
std::shared_ptr<Person> ptr(person);
这种方式将转换时,person的所指对象必须是堆上申请的,如果用栈上的变量,就会出问题。
Person person;
std::shared_ptr<Person> ptr(&person);
这段代码在退出person作用域时会出现问题,因为局部对象会被自动释放,而当share_ptr检查到引用计数为0时也会释放,相当于一个对象被释放了两次。
- =操作
=
将一个share_ptr拷贝到另一个,两个公用一个地址,引用计数+1
int main()
{
Person *person = new Person;
std::shared_ptr<Person> ptr1(person);
cout << ptr1.use_count() << endl;
std::shared_ptr<Person> ptr2 = ptr1;
cout << ptr1.use_count() << " " << ptr2.use_count() << endl;
cout << (ptr1.get() == ptr2.get()) << endl;
}
上面代码输出
create #0 person object
1
2 2
1
destory #0 person object
由此可以,ptr1和ptr2引用了同一个地址
- reset操作
如果reset的指针已经引用了其他对象,则在执行reset的时候会先释放原有对象,即原有对应引用计数-1
Person *person = new Person;
std::shared_ptr<Person> ptr1(person);
cout << ptr1.use_count() << endl;
std::shared_ptr<Person> ptr2 = ptr1;
cout << ptr1.use_count() << " " << ptr2.use_count() << endl;
cout << (ptr1.get() == ptr2.get()) << endl;
ptr2.reset(new Person());
cout << ptr1.use_count() << " " << ptr2.use_count() << endl;
cout << (ptr1.get() == ptr2.get()) << endl;
上述代码输出
create #0 person object
1
2 2
1
create #1 person object
1 1
0
destory #1 person object
destory #0 person object
执行reset后,ptr1和ptr2指向了不同的地址
- make_shared操作
int main()
{
std::shared_ptr<Person> ptr = std::make_shared<Person>();
ptr->say();
}
代码输出
create #0 person object
I'm #0 person, my name is
destory #0 person object
创建指针的是推荐使用make_shared方法。
删除
删除可以分为reset释放删除和赋值null删除,这两者原理其实是一样的,都是释放控制权,引用计数-1,如果计数为0,则释放内存
reset
释放指针的控制权
int main()
{
std::shared_ptr<Person> ptr = std::make_shared<Person>();
cout << ptr.use_count() << endl;
ptr.reset();
cout << ptr.use_count() << endl;
}
上面代码输出
create #0 person object
1
destory #0 person object
0
- 赋值null删除
将上段代码中的ptr.reset
替换为ptr = nullptr
即可,输出结果完全一样
自定义删除器
这里分为两种情况,一种时析构函数中不能完成指定的逻辑,一种是创建指针的时候传入了数组。这时需要传入我们自定义的删除器,当在析构对象的时候就不再调用析构函数了,而是调传入的删除器。
void deleter(Person *p)
{
cout << "my destory" << endl;
delete p;
}
int main()
{
Person *p = new Person;
std::shared_ptr<Person> ptr(p, deleter);
}
如果在创建share_ptr的时候不传入删除器,则在释放对象的时候会调用定义的析构函数。但如果在创建的时候传入一个删除器,则在析构的时候会调用删除器,而不直接调析构函数了。
假如初始化的时候传入了个数组,如下
int main()
{
Person *p = new Person[4];
std::shared_ptr<Person> ptr(p);
}
代码输出
create #0 person object
create #1 person object
create #2 person object
create #3 person object
destory #0 person object
可见,创建了4个人对象,但只析构了一个。在使用原始指针创建的数组的时候,如果删除的是数组,那么我们一般会写上[]符号,比如delete []a
。所以我们可以在自定义的删除器中增加delete[] 操作。
void deleter(Person *p)
{
cout << "my destory" << endl;
delete []p;
}
int main()
{
Person *p = new Person[4];
std::shared_ptr<Person> ptr(p, deleter);
}
最终输出
create #0 person object
create #1 person object
create #2 person object
create #3 person object
my destory
destory #3 person object
destory #2 person object
destory #1 person object
destory #0 person object