C++智能指针

在C++中,动态内存的管理是通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

动态内存的使用很容易出问题,因为确保在正确的时间释放内存是极其困难的。有时会忘记释放内存,在这种情况下会产生内存泄露;有时在尚有指针引用内存的情况下就释放了它,在这种情况下就会产生引用非法内存的指针。

为了更容易(同时也更安全)地使用动态内存,C++11标准库提供了两种智能指针(smart pointer)类型来管理动态对象。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指的对象。C++11标准库提供的这两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr则"独占"所指向的对象。C++11标准库还定义了一个名为weak_ptr的辅助类,它是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。智能指针是模板类而不是指针。类似vector,智能指针也是模板,当创建一个智能指针时,必须提供额外的信息即指针可以指向的类型。默认初始化的智能指针中保存着一个空指针。智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果就是检测它是否为空。

智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。

shared_ptr(共享的智能指针)

共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针 shared_ptr 是一个模板类,如果要进行初始化有三种方式:通过构造函数、std::make_shared 辅助函数以及 reset 方法。共享智能指针对象初始化完毕之后就指向了要管理的那块堆内存,如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数 use_count,函数原型如下:

// 管理当前对象的 shared_ptr 实例数量,或若无被管理对象则为 0。
long use_count() const noexcept;

智能指针实质就是重载了->和*操作符的类,由类来实现对内存的管理。

shared_ptr的类型转换不能使用一般的static_cast,这种方式进行的转换会导致转换后的指针无法再被shared_ptr对象正确的管理。应该使用专门用于shared_ptr类型转换的 static_pointer_cast<T>() , const_pointer_cast<T>() dynamic_pointer_cast<T>()

使用shared_ptr避免了手动使用delete来释放由new申请的资源,标准库也引入了make_shared函数来创建一个shared_ptr对象,使用shared_ptr和make_shared,你的代码里就可以使new和delete消失,同时又不必担心内存的泄露。

可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数(reference count)。无论何时拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。当给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。

使用shared_ptr注意事项:

(1)、不要把一个原生指针给多个shared_ptr管理;

(2)、不要把this指针给shared_ptr;

(3)、不要在函数实参里创建shared_ptr;

(4)、不要不加思考地把指针替换为shared_ptr来防止内存泄漏,shared_ptr并不是万能的,而且使用它们的话也是需要一定的开销的;

(5)、环状的链式结构shared_ptr将会导致内存泄漏(可以结合weak_ptr来解决);

(6)、共享拥有权的对象一般比限定作用域的对象生存更久,从而将导致更高的平均资源使用时间;

(7)、在多线程环境中使用共享指针的代价非常大,这是因为你需要避免关于引用计数的数据竞争;

(8)、共享对象的析构器不会在预期的时间执行;

(9)、不使用相同的内置指针值初始化(或reset)多个智能指针;

(10)、不delete get()返回的指针;

(11)、不使用get()初始化或reset另一个智能指针;

(12)、如果使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了;

(13)、如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。

shared_ptr 的初始化

1. 通过构造函数初始化

// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);

测试代码

class Test
{
public:
	Test()
	{
		cout << "Test()" << endl;
	}

	~Test()
	{
		cout << "~Test()" << endl;
	}
};

int main()
{
	//使用智能指针管理一块int型的堆内存,int的值为10
	shared_ptr<int> ptrNum(new int(10));
	cout << "ptrNum use count: " << ptrNum.use_count() << endl;

	//数组需要自定义删除器
	shared_ptr<Test> ptrTest(new Test[2], [](Test* p) {
		delete[] p;
	});
	cout << "ptrTest use count: " << ptrTest.use_count() << endl;

	//空指针引用计数为0
	shared_ptr<char> ptrChar(nullptr);
	cout << "ptrChar use count: " << ptrChar.use_count() << endl;
	return 0;
}

如果智能指针被初始化了一块有效内存,那么这块内存的引用计数 + 1,如果智能指针没有被初始化或者被初始化为 nullptr 空指针,引用计数不会 + 1。另外,不要使用一个原始指针初始化多个 shared_ptr。

由于不存在shared_ptr<T[]>,我们无法使用[]来访问数组中的元素,实际上无法访问到数组中的元素。也就是说使用shared_ptr来指向数组意义并不大。若想要数组在多个shared_ptr之间共享,可以考虑使用shared_ptr<vector>shared_ptr<array>。如果一定要使用可以自定义删除器来正确的释放内存

2. 通过拷贝和移动构造函数初始化

测试代码

int main()
{
	//使用智能指针管理一块int型的堆内存,int的值为10
	shared_ptr<int> ptrNum1(new int(10));
	cout << "ptrNum1 use count: " << ptrNum1.use_count() << endl;

	//调用拷贝构造函数
	shared_ptr<int> ptrNum2 = ptrNum1;
	cout << "ptrNum1 use count: " << ptrNum1.use_count() << "  ptrNum2 use count: " << ptrNum2.use_count() << endl;
	shared_ptr<int> ptrNum3(ptrNum1);
	cout << "ptrNum1 use count: " << ptrNum1.use_count() << "  ptrNum2 use count: " << ptrNum2.use_count()
		<< "  ptrNum3 use count: " << ptrNum3.use_count() << endl;

	//移动构造函数
	shared_ptr<int> ptrNum4 = move(ptrNum1);
	cout << "ptrNum1 use count: " << ptrNum1.use_count() << "  ptrNum2 use count: " << ptrNum2.use_count()
		<< "  ptrNum3 use count: " << ptrNum3.use_count() << "  ptrNum4 use count: " << ptrNum4.use_count() << endl;

	shared_ptr<int> ptrNum5(move(ptrNum2));
	cout << "ptrNum1 use count: " << ptrNum1.use_count() << "  ptrNum2 use count: " << ptrNum2.use_count()
		<< "  ptrNum3 use count: " << ptrNum3.use_count() << "  ptrNum4 use count: " << ptrNum4.use_count() 
		<< "  ptrNum5 use count: " << ptrNum5.use_count() << endl;
	return 0;
}

1>ptrNum1 use count: 1
1>ptrNum1 use count: 2  ptrNum2 use count: 2
1>ptrNum1 use count: 3  ptrNum2 use count: 3  ptrNum3 use count: 3
1>ptrNum1 use count: 0  ptrNum2 use count: 3  ptrNum3 use count: 3  ptrNum4 use count: 3
1>ptrNum1 use count: 0  ptrNum2 use count: 0  ptrNum3 use count: 3  ptrNum4 use count: 3  ptrNum5 use count: 3

果使用拷贝的方式初始化共享智能指针对象,这两个对象会同时管理同一块堆内存,堆内存对应的引用计数也会增加;如果使用移动的方式初始智能指针对象,只是转让了内存的所有权,管理内存的对象并不会增加,因此内存的引用计数不会变化。

3. 通过 std::make_shared 初始化

通过 C++ 提供的 std::make_shared() 就可以完成内存对象的创建并将其初始化给智能指针,函数原型如下: 

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );

T:模板参数的数据类型
Args&&... args :要初始化的数据,如果是通过 make_shared 创建对象,需按照构造函数的参数列表指定

class Test
{
public:
	Test()
	{
		cout << "Test()" << endl;
	}

	Test(string strName)
	{
		cout << "Test(string strName)   " << strName << endl;
	}

	Test(string strName, int num)
	{
		cout << "Test(string strName, int num)" << endl;
	}
};
int main()
{
	//使用智能指针管理一块int型的堆内存,内部引用计数为1
	shared_ptr<int> ptrNum = make_shared<int>(20);
	cout << "ptrNum use count: " << ptrNum.use_count() << endl;

	shared_ptr<Test> ptrTest1 = make_shared<Test>();
	cout << "ptrTest1 use count: " << ptrTest1.use_count() << endl;

	shared_ptr<Test> ptrTest2 = make_shared<Test>("123");
	cout << "ptrTest2 use count: " << ptrTest2.use_count() << endl;

	shared_ptr<Test> ptrTest3 = make_shared<Test>("123", 2);
	cout << "ptrTest3 use count: " << ptrTest3.use_count() << endl;
	return 0;
}

使用 std::make_shared() 模板函数可以完成内存地址的创建,并将最终得到的内存地址传递给共享智能指针对象管理。如果申请的内存是普通类型,通过函数的()可完成地址的初始化,如果要创建一个类对象,函数的()内部需要指定构造对象需要的参数,也就是类构造函数的参数。

4. 通过 reset 方法初始化

共享智能指针类提供的 std::shared_ptr::reset 方法函数原型如下:

void reset() noexcept;

template< class Y >
void reset( Y* ptr );

template< class Y, class Deleter >
void reset( Y* ptr, Deleter d );

template< class Y, class Deleter, class Alloc >
void reset( Y* ptr, Deleter d, Alloc alloc );

ptr:指向要取得所有权的对象的指针
d:指向要取得所有权的对象的指针
aloc:内部存储所用的分配器

int main()
{
	//使用智能指针管理一块int型的堆内存,int的值为10
	shared_ptr<int> ptrNum1(new int(10));
	cout << "ptrNum1 use count: " << ptrNum1.use_count() << endl;

	//ptr1 ptr2全指向同一片内存 引用计数为2
	shared_ptr<int> ptrNum2 = ptrNum1;
	cout << "ptrNum1 use count: " << ptrNum1.use_count() << "  ptrNum2 use count: " << ptrNum2.use_count() << endl;

	//重置ptrNum2 ptrNum1指向的内存引用计数-1   ptrNum2指针为空,引用计数为0
	ptrNum2.reset();
	cout << "ptrNum1 use count: " << ptrNum1.use_count() << "  ptrNum2 use count: " << ptrNum2.use_count() << endl;

	//ptrNum2指向一片新的内存 引用计数为1
	ptrNum2.reset(new int(20));
	cout << "ptrNum1 use count: " << ptrNum1.use_count() << "  ptrNum2 use count: " << ptrNum2.use_count() << endl;
	return 0;
}


 

对于一个未初始化的共享智能指针,可以通过 reset 方法来初始化,当智能指针中有值的时候,调用 reset 会使引用计数减 1。

获取原始指针

对应基础数据类型来说,通过操作智能指针和操作智能指针管理的内存效果是一样的,可以直接完成数据的读写。但是如果共享智能指针管理的是一个对象,那么就需要取出原始内存的地址再操作,可以调用共享智能指针类提供的 get () 方法得到原始地址,其函数原型如下:

T* get() const noexcept;
#include <iostream>
#include <string>
#include <memory>
using namespace std;

int main()
{
    int len = 128;
    shared_ptr<char> ptr(new char[len]);
    // 得到指针的原始地址
    char* add = ptr.get();
    memset(add, 0, len);
    strcpy(add, "我是要成为海贼王的男人!!!");
    cout << "string: " << add << endl;
    
    shared_ptr<int> p(new int);
    *p = 100;
    cout << *p.get() << "  " << *p << endl;
    
    return 0;
}

指定删除器

当智能指针管理的内存对应的引用计数变为 0 的时候,这块内存就会被智能指针析构掉了。另外,我们在初始化智能指针的时候也可以自己指定删除动作,这个删除操作对应的函数被称之为删除器,这个删除器函数本质是一个回调函数,我们只需要进行实现,其调用是由智能指针完成的。

#include <iostream>
#include <memory>
using namespace std;

// 自定义删除器函数,释放int型内存
void deleteIntPtr(int* p)
{
    delete p;
    cout << "int 型内存被释放了...";
}

int main()
{
    shared_ptr<int> ptr(new int(250), deleteIntPtr);
    return 0;
}

在 C++11 中使用 shared_ptr 管理动态数组时,需要指定删除器,因为 std::shared_ptr的默认删除器不支持数组对象,具体的处理代码如下:

int main()
{
    shared_ptr<int> ptr(new int[10], [](int* p) {delete[]p; });
    return 0;
}
int main()
{
    shared_ptr<int> ptr(new int[10], default_delete<int[]>());
    return 0;
}

另外,我们还可以自己封装一个 make_shared_array 方法来让 shared_ptr 支持数组,代码如下:

#include <iostream>
#include <memory>
using namespace std;

template <typename T>
shared_ptr<T> make_share_array(size_t size)
{
    // 返回匿名对象
    return shared_ptr<T>(new T[size], default_delete<T[]>());
}

int main()
{
    shared_ptr<int> ptr1 = make_share_array<int>(10);
    cout << ptr1.use_count() << endl;
    shared_ptr<char> ptr2 = make_share_array<char>(128);
    cout << ptr2.use_count() << endl;
    return 0;
}

weak_ptr(弱引用的智能指针,它不共享指针,不能操作资源,是用来监视 shared_ptr 的)

weak_ptr被设计为与shared_ptr共同工作,可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。同样,在weak_ptr析构时也不会导致引用计数的减少,它只是一个静静地观察者。weak_ptr没有重载operator*和->,这是特意的,因为它不共享指针,不能操作资源,这是它弱的原因。但它可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象,从而操作资源。

weak_ptr用于解决”引用计数”模型循环依赖问题,weak_ptr指向一个对象,并不增减该对象的引用计数器。weak_ptr用于配合shared_ptr使用,并不影响动态对象的生命周期,即其存在与否并不影响对象的引用计数器。weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象。weak_ptr提供了expired()与lock()成员函数,前者用于判断weak_ptr指向的对象是否已被销毁,后者返回其所指对象的shared_ptr智能指针(对象销毁时返回”空”shared_ptr)。

智能指针是模板类而不是指针.

weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shard_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。

当创建一个weak_ptr时,要用一个shared_ptr来初始化它。不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否仍存在。如果存在,lock返回一个指向共享对象的shared_ptr。与任何其它shared_ptr类似,只要此shared_ptr存在,它所指向的底层对象也就会一直存在。

初始化

// 默认构造函数
constexpr weak_ptr() noexcept;
// 拷贝构造
weak_ptr (const weak_ptr& x) noexcept;
template <class U> weak_ptr (const weak_ptr<U>& x) noexcept;
// 通过shared_ptr对象构造
template <class U> weak_ptr (const shared_ptr<U>& x) noexcept;

在 C++11 中,weak_ptr 的初始化可以通过以上提供的构造函数来完成初始化,具体使用方法如下:

int main()
{
	shared_ptr<int> sp(new int);

	//构造了一个空weak_ptr对象
	weak_ptr<int> wp1;
	
	//通过一个空weak_ptr对象构造了另一个空weak_ptr对象
	weak_ptr<int> wp2(wp1);

	//通过一个shared_ptr对象构造了一个可用的weak_ptr对象
	weak_ptr<int> wp3(sp);

	weak_ptr<int> wp4;
	//通过一个shared_ptr对象构造了一个可用的weak_ptr对象
	wp4 = sp;

	weak_ptr<int> wp5;
	//通过一个weak_ptr对象构造了一个可用的weak_ptr对象
	wp5 = wp3;

	return 0;
}

其他常用方法

use_count()

过调用 std::weak_ptr 类提供的 use_count() 方法可以获得当前所观测资源的引用计数,函数原型如下:

// 函数返回所监测的资源的引用计数
long int use_count() const noexcept;
int main()
{
	shared_ptr<int> sp(new int);

	//构造了一个空weak_ptr对象
	weak_ptr<int> wp1;
	
	//通过一个空weak_ptr对象构造了另一个空weak_ptr对象
	weak_ptr<int> wp2(wp1);

	//通过一个shared_ptr对象构造了一个可用的weak_ptr对象
	weak_ptr<int> wp3(sp);

	weak_ptr<int> wp4;
	//通过一个shared_ptr对象构造了一个可用的weak_ptr对象
	wp4 = sp;

	weak_ptr<int> wp5;
	//通过一个weak_ptr对象构造了一个可用的weak_ptr对象
	wp5 = wp3;

	cout << "use_count" << endl;
	cout << "wp1:" << wp1.use_count() << endl;
	cout << "wp2:" << wp2.use_count() << endl;
	cout << "wp3:" << wp3.use_count() << endl;
	cout << "wp4:" << wp4.use_count() << endl;
	cout << "wp5:" << wp5.use_count() << endl;
	return 0;
}

1>use_count
1>wp1:0
1>wp2:0
1>wp3:1
1>wp4:1
1>wp5:1

通过打印的结果可以知道,虽然弱引用智能指针 wp3、wp4、wp5 监测的资源是同一个,但是它的引用计数并没有发生任何的变化,也进一步证明了 weak_ptr只是监测资源,并不管理资源。

expired()

通过调用 std::weak_ptr 类提供的 expired() 方法来判断观测的资源是否已经被释放,函数原型如下:

// 返回true表示资源已经被释放, 返回false表示资源没有被释放
bool expired() const noexcept;
int main()
{
	shared_ptr<int> sp(new int);
	weak_ptr<int> wp(sp);
	cout << "weak_ptr " << (wp.expired() ? "is" : "is not") << "expired" << endl;

	sp.reset();
	cout << "weak_ptr " << (wp.expired() ? "is" : "is not") << "expired" << endl;
	return 0;
}

lock()

通过调用 std::weak_ptr 类提供的 lock() 方法来获取管理所监测资源的 shared_ptr 对象,函数原型如下:

shared_ptr<element_type> lock() const noexcept;
int main()
{
	shared_ptr<int> sp1, sp2;
	weak_ptr<int> wp;

	sp1 = make_shared<int>(10);
	wp = sp1;
	sp2 = wp.lock();
	cout << "use_cout:" << wp.use_count() << endl;

	sp1.reset();
	cout << "use_cout:" << wp.use_count() << endl;

	sp1 = wp.lock();
	cout << "use_cout:" << wp.use_count() << endl;

	cout << "sp1:" << *sp1 << endl;
	cout << "sp2:" << *sp1 << endl;
	return 0;
}

reset()

通过调用 std::weak_ptr 类提供的 reset() 方法来清空对象,使其不监测任何资源,函数原型如下:

void reset() noexcept;
#include <iostream>
#include <memory>
using namespace std;

int main() 
{
    shared_ptr<int> sp(new int(10));
    weak_ptr<int> wp(sp);
    cout << "1. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;

    wp.reset();
    cout << "2. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;

    return 0;
}

返回管理 this 的 shared_ptr

如果在一个类中编写了一个函数,通过这个得到管理当前对象的共享智能指针,我们可能会写出如下代码:

struct Test
{
	shared_ptr<Test> getSharedPtr()
	{
		return shared_ptr<Test>(this);
	}

	~Test()
	{
		cout << "class Test is disStruct..." << endl;
	}
};

int main()
{
	shared_ptr<Test> sp1(new Test);
	cout << "use_cout: " << sp1.use_count() << endl;
	shared_ptr<Test> sp2 = sp1->getSharedPtr();
	cout << "use_cout: " << sp1.use_count() << endl;
	return 0;
}

执行上面的测试代码,运行中会出现异常,在终端还是能看到对应的日志输出:

1>use_cout: 1
1>use_cout: 1
1>class Test is disStruct...
1>class Test is disStruct...

通过输出的结果可以看到一个对象被析构了两次,其原因是这样的:在这个例子中使用同一个指针 this 构造了两个智能指针对象 sp1 和 sp2,这二者之间是没有任何关系的,因为 sp2 并不是通过 sp1 初始化得到的实例对象。在离开作用域之后 this 将被构造的两个智能指针各自析构,导致重复析构的错误。

这个问题可以通过 weak_ptr 来解决,通过 wek_ptr 返回管理 this 资源的共享智能指针对象 shared_ptr。C++11 中为我们提供了一个模板类叫做 std::enable_shared_from_this<T>,这个类中有一个方法叫做 shared_from_this(),通过这个方法可以返回一个共享智能指针,在函数的内部就是使用 weak_ptr 来监测 this 对象,并通过调用 weak_ptr 的 lock() 方法返回一个 shared_ptr 对象。

struct Test : public enable_shared_from_this<Test>
{
	shared_ptr<Test> getSharedPtr()
	{
		return shared_from_this();
	}

	~Test() 
	{
		cout << "class Test is disStruct ..." << endl;
	}
};

int main()
{
	shared_ptr<Test> sp1(new Test);
	cout << "use_cout: " << sp1.use_count() << endl;
	shared_ptr<Test> sp2 = sp1->getSharedPtr();
	cout << "use_cout: " << sp1.use_count() << endl;
	return 0;
}

最后需要强调一个细节:在调用 enable_shared_from_this 类的 hared_from_this () 方法之前,必须要先初始化函数内部 weak_ptr 对象,否则该函数无法返回一个有效的 shared_ptr 对象(具体处理方法可以参考上面的示例代码)。

unique_ptr(独占的智能指针)

std::unique_ptr是C++11标准中用来取代std::auto_ptr的指针容器(在C++11中,auto_ptr被废弃)。它不能与其它unique_ptr类型的指针对象共享所指对象的内存。这种”所有权”仅能够通过标准库的move函数来转移。unique_ptr是一个删除了拷贝构造函数、保留了移动构造函数的指针封装类型。

一个unique_ptr"拥有"它所指向的对象。与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。与shared_ptr不同,在C++11中,没有类似make_shared的标准库函数返回一个unique_ptr。当定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。类似shared_ptr,初始化unique_ptr必须采用直接初始化形式。由于一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作。虽然不能拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique。

调用release会切断unique_ptr和它原来管理的对象间的联系。release返回的指针通过被用来初始化另一个智能指针或给另一个智能指针赋值。如果不用另一个智能指针来保存release返回的指针,程序就要负责资源的释放。

不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr,最常见的例子是从函数返回一个unique_ptr。

类似shared_ptr,unique_ptr默认情况下用delete释放它指向的对象。与shared_ptr一样,可以重载一个unique_ptr中默认的删除器。

初始化

// 通过构造函数初始化对象
unique_ptr<int> ptr1(new int(10));
// error, 不允许将一个unique_ptr赋值给另一个unique_ptr
unique_ptr<int> ptr2 = ptr1;

std::unique_ptr 不允许复制,但是可以通过函数返回给其他的 std::unique_ptr,还可以通过 std::move 来转译给其他的 std::unique_ptr,这样原始指针的所有权就被转移了,这个原始指针还是被独占的。

unique_ptr<int> func()
{
	//编译器会去调用 移动构造函数
	return unique_ptr<int>(new int(520));
}

int main()
{
	//通过构造函数初始化
	unique_ptr<int> ptr1(new int(10));

	//通过转移所有权的方式初始化
	unique_ptr<int> ptr2 = move(ptr1);
	unique_ptr<int> ptr3 = func();

	return 0;
}

unique_ptr 独占智能指针类也有一个 reset 方法,函数原型如下:

void reset( pointer ptr = pointer() ) noexcept;

使用 reset 方法可以让 unique_ptr 解除对原始内存的管理,也可以用来初始化一个独占的智能指针。

int main()
{
	//通过构造函数初始化
	unique_ptr<int> ptr1(new int(10));
	unique_ptr<int> ptr2 = move(ptr1);

	//解除对原始内存的管理
	ptr1.reset();
	//重新指定智能指针管理的原始内存
	ptr2.reset(new int(250));

	return 0;
}

如果想要获取独占智能指针管理的原始地址,可以调用 get () 方法,函数原型如下

pointer get() const noexcept;

int main()
{
    unique_ptr<int> ptr1(new int(10));
    unique_ptr<int> ptr2 = move(ptr1);

    ptr2.reset(new int(250));
    cout << *ptr2.get() << endl;	// 得到内存地址中存储的实际数值 250

    return 0;
}

指定删除器

unique_ptr 指定删除器和 shared_ptr 指定删除器是有区别的,unique_ptr 指定删除器的时候需要确定删除器的类型,所以不能像 shared_ptr 那样直接指定删除器,举例说明:

int main()
{
	shared_ptr<int> ptr1(new int(10), [](int* p) { delete p; });
	//error 没有指定删除器类型
	unique_ptr<int> ptr2(new int(10), [](int* p) { delete p; });

	//函数指针类型
	using funPtr = void(*)(int*);
	//正确 指定了删除器类型
	unique_ptr<int, funPtr> ptr3(new int(10), [](int* p) { delete p; });


	return 0;
}

func_ptr 的类型和 lambda表达式的类型是一致的。在 lambda 表达式没有捕获任何变量的情况下是正确的,如果捕获了变量,编译时则会报错:

	//报错
	unique_ptr<int, funPtr> ptr4(new int(10), [&](int* p) { delete p; });

上面的代码中错误原因是这样的,在 lambda 表达式没有捕获任何外部变量时,可以直接转换为函数指针,一旦捕获了就无法转换了,如果想要让编译器成功通过编译,那么需要使用可调用对象包装器来处理声明的函数指针:

#include <iostream>
#include <memory>
#include <functional>

using namespace std;

int main()
{
	unique_ptr<int, function<void(int*)>> ptr(new int(10), [&](int* p) { delete p; });

	return 0;
}

管理数组类型的地址

int main()
{
	unique_ptr<int[]> ptr(new int[5]);

	//C++11之后 shared_ptr也支持了数组
	shared_ptr<int[]> ptr1(new int[5]);
	return 0;
}

数组的智能指针的限制: 

1,unique_ptr的数组智能指针,没有*和->操作,但支持下标操作[]

2,shared_ptr的数组智能指针,有*和->操作,但不支持下标操作[],只能通过get()去访问数组的元素。

3,shared_ptr的数组智能指针,必须要自定义deleter 或者使用helpestd::shared_ptr<int> p(new int[10],std::default_delete<int[]>()); 

#include <iostream>
#include <memory>
#include <vector>

using namespace std;

class test{
public:
  explicit test(int d = 0) : data(d){cout << "new" << data << endl;}
  ~test(){cout << "del" << data << endl;}
  void fun(){cout << data << endl;}
public:
  int data;
};
int main(){
  //test* t = new test[2];                                                      
  unique_ptr<test[]> up(new test[2]);
  up[0].data = 1;
  up[0].fun();
  up[1].fun();
  shared_ptr<test> sp(new test[2], [](test* p){delete [] p;});
  (sp.get())->data = 2;//数组的第一个元素                                       
  sp->data = 10;
  test& st = *sp;
  st.data = 20;
  (sp.get() + 1)->data = 3;//数组的第二个元素                                   
  return 0;
}

数组的初始化问题

std::unique_ptr<char[]> ptrOutBuffer(new char[32 * 1024]);

这种写法不会帮你初始化,需要自己手动初始化

memset(ptrOutBuffer.get(), 0, 32 * 1024);

std::unique_ptr<char[]> ptrOutBuffer(new char[32 * 1024]());               //int 类型也是一样  其它类型没有验证过

这种写法会帮你把数组置0

std::unique_ptr<char[]> ptrOutBuffer = std::make_unique<char[]>(32 * 1024);   内部帮你memset为0

智能指针的使用建议

1. 我们大多数场景下用到的应该都是 unique_ptr。unique_ptr 代表的是专属所有权, 不需要转移所有权

2. shared_ptr 通常使用在共享权不明的场景。有可能多个对象同时管理同一个内存时

智能指针传递首先考虑的是所有权

1. 参数类型使用 const shared_ptr<A> & 或 A *。如果函数内可能传递共享权,用 const shared_ptr<A> &;否则,用 A *;

2. 必须在一个外部 shared_ptr (通常是在栈上)的保护下,传递 A* 或 const shared_ptr<A> & 给函数做参数;

3. 在调用函数中,根据目的,构造出 weak_ptr 或 shared_ptr 来传递共享权。



 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值