往期地址:
- c++系列一 —— c++的封装
- c++系列二 —— c++的继承
- c++系列三 —— 继承和多态特性
- c++系列四 —— 运算符重载
- c++系列五 —— 静态成员和静态类
- c++系列六 —— 友元函数和友元类
- c++系列七 —— STL编程之模板template
- c++系列八 —— STL编程之容器类
本期主题:
智能指针
智能指针
1.智能指针概述
1.解决了什么问题,智能在哪里
智能指针解决的问题:
解决了内存泄漏的问题,对于申请在
堆上
的内存(c语言是malloc,c++是new),程序员不需要主动释放,智能指针自动释放
2.对智能指针的原理猜想
- 我们都知道栈上的内存能够自动释放,所以猜测智能指针的底层应该是类似于栈的方式
- 栈上的变量,创建时自动调用构造函数,退出时自动调用析构函数
按照这样的方式我们来写一份测试代码:
#include <iostream>
using namespace std;
//构造函数申请内存,析构函数释放
class smart_pointer
{
public:
smart_pointer()
{
cout << "smart_pointer()" << endl;
p = NULL;
}
smart_pointer(int size)
{
cout << "smart_pointer(int size)" << endl;
p = new int[size];
}
~smart_pointer()
{
cout << "~smart_pointer()" << endl;
if (NULL != p)
delete[] p;
}
void test_func()
{
if (p != NULL) {
p[0] = 1;
cout << "p[0] val is " << p[0] << endl;
}
}
private:
int *p;
};
int main(void)
{
//这里选择栈上的局部变量,这样就能够自动调用构造和析构函数
smart_pointer sp_instance(3);
sp_instance.test_func();
return 0;
}
运行结果:
2.智能指针使用
详细内容可以参考cppreference的动态内存管理章节中,cpprefernce动态内存管理
2.1 auto_ptr
auto_ptr定义:
template< class T > class auto_ptr;
使用示例:
#include <iostream>
#include <memory>
class people
{
public:
people() {
cout << "people()" << endl;
}
people(string name) {
this->name = name;
cout << "people(string name)" << endl;
}
~people() {
cout << "~people()" << endl;
}
void print_name(void)
{
cout << "name: " << this->name << endl;
}
private:
string name;
};
int main(void)
{
//p1是people对象的智能指针
auto_ptr<people> p1(new people("jason"));
p1->print_name();
}
运行结果:
//这里需要使用 --std=c++98的原因是 autoptr在C++17中移除了,所以需要告诉编译器用c++98的方式去编译,不然会编译报错
2.2 unique_ptr
unique_ptr的定义如下:
template<
class T,
class Deleter = std::default_delete<T>
> class unique_ptr;
1. 构造函数使用
unique_ptr有多种构造函数,详细的描述参考cpprefernce,这里我们讲几种:
struct Foo {
Foo() {cout << "Foo default ctor\n";}
~Foo() {cout << "~Foo dtor\n";}
};
//删除器
struct D {
D() {cout << "D default ctor\n";}
D(const D&) {cout << "D copy ctor\n";}
void operator()(Foo* p) const {
cout << "D is deleting Foo\n";
delete p;
};
};
int main(void)
{
cout << "Example constructor(1)...\n";
unique_ptr<Foo> up1;
unique_ptr<Foo> up1b(nullptr);
cout << "Example constructor(2)...\n";
//添加代码括号,改变up2变量的生命周期,退出括号时就释放up2
{
unique_ptr<Foo> up2(new Foo);
}
cout << "Example constructor(3)...\n";
D d;
{
//d是类D的对象,up3的构造函数以d作为了参数,所以会调用D的复制构造函数
unique_ptr<Foo, D> up3(new Foo, d);
}
{
//传递的参数已经是引用了,不需要像上面一样再调用复制构造
unique_ptr<Foo, D&> up4(new Foo, d);
}
//3个数组,构造调用3次,析构也会调用3次
cout << "Example array constructor...\n";
{
unique_ptr<Foo[]> up(new Foo[3]);
}
return 0;
}
测试结果:
$ ./a.out
Example constructor(1)...
Example constructor(2)...
Foo default ctor
~Foo dtor
Example constructor(3)...
D default ctor
Foo default ctor
D copy ctor
D is deleting Foo
~Foo dtor
Foo default ctor
D is deleting Foo
~Foo dtor
Example array constructor...
Foo default ctor
Foo default ctor
Foo default ctor
~Foo dtor
~Foo dtor
~Foo dtor
2. 管理器 release/reset/swap
这几个管理去都比较简单,直接看代码即可:
struct Foo {
int val;
Foo() {cout << "Foo default ctor\n";}
Foo(int _val):val(_val) {cout << "Foo int val\n";}
~Foo() {cout << "~Foo dtor\n";}
};
//删除器
struct D {
D() {cout << "D default ctor\n";}
D(const D&) {cout << "D copy ctor\n";}
void operator()(Foo* p) const {
cout << "D is deleting Foo\n";
delete p;
};
};
int main(void)
{
//1.release 释放被管理对象
cout << "Example 1, release....\n";
//创建被管理对象的指针
unique_ptr<Foo> up(new Foo());
//使用release,释放被管理对象的所有权,给另外一个指针(另外一个指针并非智能指针)
Foo *fp = up.release();
assert (up.get() == nullptr);
cout << "Foo is no longer owned by unique_ptr...\n";
delete fp;
//2.reset 替换被管理对象
//与release的区别在于,这里会先析构掉旧的智能指针,然后创建新的
cout << "Example 2, reset....\n";
unique_ptr<Foo> up2(new Foo());
cout << "new Foo, start reset--------\n";
up2.reset(new Foo());
cout << "new Foo, reset done--------\n";
//3.swap,用来交换 *this和另一unique_ptr对象other所管理的内容
//void swap(unique_ptr& other) noexcept;
cout << "Example 3, swap....\n";
unique_ptr<Foo> up3a(new Foo(1));
unique_ptr<Foo> up3b(new Foo(2));
up3a.swap(up3b);
cout << "up3a->val:" << up3a->val << endl;
cout << "up3b->val:" << up3b->val << endl;
return 0;
}
测试结果:
Example 1, release....
Foo default ctor
Foo is no longer owned by unique_ptr...
~Foo dtor
Example 2, reset....
Foo default ctor
new Foo, start reset--------
Foo default ctor
~Foo dtor
new Foo, reset done--------
Example 3, swap....
Foo int val
Foo int val
up3a->val:2
up3b->val:1
~Foo dtor
~Foo dtor
~Foo dtor
3. 观察器 get/get_deleter/operator bool
直接看代码:
struct Foo {
int val;
Foo() {cout << "Foo default ctor\n";}
Foo(int _val):val(_val) {cout << "Foo int val\n";}
~Foo() {cout << "~Foo dtor\n";}
};
//删除器
struct D {
void test_func(void) {
cout << "test_func" << endl;
};
D() {cout << "D default ctor\n";}
D(const D&) {cout << "D copy ctor\n";}
void operator()(Foo* p) const {
cout << "D is deleting Foo\n";
delete p;
};
};
int main(void)
{
unique_ptr<string> up1(new string("hello"));
string *p1 = up1.get();
cout << *p1 << endl;
cout << "Example 2, get_deleter ....\n";
//2.get_deleter返回被管理对象的删除器
unique_ptr<Foo, D> up2(new Foo, D());
D& d = up2.get_deleter();
d.test_func();
//3.operator bool,就是将指针对象进行运算符重载,重载成一个bool量
cout << "Example 3, operator bool ....\n";
if ((up1) && (up2))
{
cout << true << endl;
}
return 0;
}
测试结果:
$ ./a.out
Example 1, get....
hello
Example 2, get_deleter ....
D default ctor
Foo default ctor
D copy ctor
test_func
Example 3, operator bool ....
1
D is deleting Foo
~Foo dtor
2.3 auto_ptr和unique_ptr对比
unique_ptr作为auto_ptr的升级版,优势具体在哪里,我们做实验来进行对比。
1.赋值运算
差异:unique_ptr比auto_ptr的赋值运算更为安全;
- auto_ptr的赋值运算符等效于调用
reset(r.release())
,实际上是把智能指针的所有权转移
,原智能指针被release之后已经变成nullptr; - unique_ptr 不允许直接赋值,需要使用移动赋值;
1.autoptr例子:
//1.auto_ptr的例子,直接赋值
class people
{
public:
people() {
cout << "people()" << endl;
}
people(string name) {
this->name = name;
cout << "people(string name)" << endl;
}
~people() {
cout << "~people()" << endl;
}
void print_name(void)
{
cout << "name: " << this->name << endl;
}
private:
string name;
};
void test_func(auto_ptr<people> ptr)
{
ptr->print_name();
}
int main(void)
{
//1.测试1:直接使用=赋值运算符,等效于调用 reset(r.release())
auto_ptr<people> p1(new people("jason"));
auto_ptr<people> p2 = p1;
p2->print_name();
p1->print_name(); //这里发生了core dumped,发生coredump的原因是此时p1已经是Nullptr,被reset(nullptr)了
}
测试结果:
$ ./a.out
people(string name)
name: jason
Segmentation fault (core dumped)
2.unique_ptr例子:
struct Foo {
int val;
Foo() {cout << "Foo default ctor\n";}
Foo(int _val):val(_val) {cout << "Foo int" << this->val <<endl;}
~Foo() {cout << "~Foo dtor\n";}
void print_val() {
cout << "val is" << this->val << endl;
}
};
//删除器
struct D {
void test_func(void) {
cout << "test_func" << endl;
};
D() {cout << "D default ctor\n";}
D(const D&) {cout << "D copy ctor\n";}
void operator()(Foo* p) const {
cout << "D is deleting Foo\n";
delete p;
};
};
int main(void)
{
unique_ptr<Foo> up(new Foo(4));
// unique_ptr<Foo> up2 = up; //unique_ptr不允许直接赋值,error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Foo; _Dp = std::default_delete<Foo>]’
unique_ptr<Foo> up2 = move(up);
up2->print_val();
return 0;
}
测试结果:
2.作为函数参数的差异
差异:
- auto_ptr作为函数参数时,只能传引用,而不能传值,传值会导致一次指针的赋值,导致指针变空;
- unique_ptr作为函数参数时,既能传引用,又能传值;
1.autoptr例子:
测试代码:
使用 test_func 时,后面会发生coredump,使用 test_func_re()就可以,原因是因为传值作为参数时,会产生一个临时对象来接受参数
,其实又回到第一个差异,赋值的问题。
class people
{
public:
people() {
cout << "people()" << endl;
}
people(string name) {
this->name = name;
cout << "people(string name)" << endl;
}
~people() {
cout << "~people()" << endl;
}
void print_name(void)
{
cout << "name: " << this->name << endl;
}
private:
string name;
};
void test_func(auto_ptr<people> ptr)
{
ptr->print_name();
}
void test_func_re(auto_ptr<people>& ptr)
{
ptr->print_name();
}
int main(void)
{
//2.测试2:auto_ptr作为函数的参数
auto_ptr<people> p1(new people("jason"));
// test_func(p1); //在这里就发生了析构
// p1->print_name(); //core dumped
test_func_re(p1);
}
测试结果:
2.unique_ptr例子:
struct Foo {
int val;
Foo() {cout << "Foo default ctor\n";}
Foo(int _val):val(_val) {cout << "Foo int" << this->val <<endl;}
~Foo() {cout << "~Foo dtor\n";}
void print_val() {
cout << "val is" << this->val << endl;
}
};
//删除器
struct D {
void test_func(void) {
cout << "test_func" << endl;
};
D() {cout << "D default ctor\n";}
D(const D&) {cout << "D copy ctor\n";}
void operator()(Foo* p) const {
cout << "D is deleting Foo\n";
delete p;
};
};
void test_arg_func(unique_ptr<Foo> ptr)
{
ptr->print_val();
}
int main(void)
{
unique_ptr<Foo> up(new Foo(4));
test_arg_func(move(up));
return 0;
}