c++智能指针

auto_ptr

auto_ptr 是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new 获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用delete 来释放内存!

用法

// 引入头文件
#include 

// auto_ptr<类型> 变量名(new 类型)
// 例如:
auto_ptr< string > str(new string(“啊哈哈哈,呵呵呵!”));
auto_ptr> av(new vector< int >());
auto_ptr< int > array(new int[10]);
class Test {
public:
	Test() { cout << "Test构造函数..." << endl; }
	~Test() { cout << "Test析构函数..." << endl; }
	int getVal() { return this->val; }
private:
	int val = 5;
};

/************************************************************/
// 定义智能指针
auto_ptr test(new Test);

/************************************************************/
// 智能指针可以像普通指针那样使用
cout << "test->debug:" << test->getVal() << endl;
cout << "(*test).debug:" << (*test).getVal() << endl;

常用函数

get() 获取智能指针托管的指针地址

// 定义智能指针
auto_ptr test(new Test);

Test *tmp = test.get();		// 获取指针返回
cout << "tmp->debug:" << tmp->getVal() << endl;

release() 取消智能指针对动态内存的托管

// 定义智能指针
auto_ptr test(new Test);
Test *tmp2 = test.release();	// 取消智能指针对动态内存的托管
delete tmp2;	// 之前分配的内存需要自己手动释放

也就是智能指针不再对该指针进行管理,改由管理员进行管理

reset() 重置智能指针托管的内存地址

重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉

// 定义智能指针
auto_ptr test(new Test);
test.reset();			// 释放掉智能指针托管的指针内存,并将其置NULL
test.reset(new Test());	// 释放掉智能指针托管的指针内存,并将参数指针取代之

注意事项

  • 尽可能不要将auto_ptr 变量定义为全局变量或指针

  • C++11 后auto_ptr 已经被“抛弃”,已使用unique_ptr替代!C++11后不建议使用auto_ptr

    • auto_ptr 被C++11抛弃的主要原因

      • 复制或者赋值都会改变资源的所有权
      // auto_ptr 被C++11抛弃的主要原因
      auto_ptr p1(new string("I'm Li Ming!"));
      auto_ptr p2(new string("I'm age 22."));
      
      cout << "p1:" << p1.get() << endl;
      cout << "p2:" << p2.get() << endl;
      
      // p2赋值给p1后,首先p1会先将自己原先托管的指针释放掉,然后接收托管p2所托管的指针,
      // 然后p2所托管的指针制NULL,也就是p1托管了p2托管的指针,而p2放弃了托管。
      p1 = p2;	
      cout << "p1 = p2 赋值后:" << endl;
      cout << "p1:" << p1.get() << endl;
      cout << "p2:" << p2.get() << endl;
      
      • 在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制和可赋值

        vector> vec;
        auto_ptr p3(new string("I'm P3"));
        auto_ptr p4(new string("I'm P4"));
        
        // 必须使用std::move修饰成右值,才可以进行插入容器中
        vec.push_back(std::move(p3));
        vec.push_back(std::move(p4));
        
        cout << "vec.at(0):" <<  *vec.at(0) << endl;
        cout << "vec[1]:" <<  *vec[1] << endl;
        
        // 风险来了:
        vec[0] = vec[1];	// 如果进行赋值,问题又回到了上面一个问题中。
        cout << "vec.at(0):" << *vec.at(0) << endl;
        cout << "vec[1]:" << *vec[1] << endl;
        
      • 不支持对象数组的内存管理

        auto_ptr array(new int[5]);	// 不能这样定义
        

unique_ptr

unique_ptr 和 auto_ptr用法几乎一样,除了一些特性。

  • 基于排他所有权模式:两个指针不能指向同一个资源
  • 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
  • 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
  • 在容器中保存指针是安全的

无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值

unique_ptr p1(new string("I'm Li Ming!"));
unique_ptr p2(new string("I'm age 22."));
	
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;

p1 = p2;// error					// 禁止左值赋值
unique_ptr p3(p2);//error	// 禁止左值赋值构造

unique_ptr p3(std::move(p1)); // 使用move
p1 = std::move(p2);	// 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样

cout << "p1 = p2 赋值后:" << endl;
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;

在 STL 容器中使用unique_ptr,不允许直接赋值

vector> vec;
unique_ptr p3(new string("I'm P3"));
unique_ptr p4(new string("I'm P4"));

vec.push_back(std::move(p3));
vec.push_back(std::move(p4));

cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;

vec[0] = vec[1];	/* error 不允许直接赋值 */
vec[0] = std::move(vec[1]);		// 需要使用move修饰,使得程序员知道后果

cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;

支持对象数组的内存管理

// 会自动调用delete [] 函数去释放内存
unique_ptr array(new int[5]);	// 支持这样定义

auto_ptr 与 unique_ptr智能指针的内存管理陷阱

auto_ptr p1;
string *str = new string("智能指针的内存管理陷阱");
p1.reset(str);	// p1托管str指针
{
	auto_ptr p2;
	p2.reset(str);	// p2接管str指针时,会先取消p1的托管,然后再对str的托管
}

// 此时p1已经没有托管内容指针了,为NULL,在使用它就会内存报错!
cout << "str:" << *p1 << endl;

这是由于auto_ptr 与 unique_ptr的排他性所导致的,为了解决这样的问题,我们可以使用shared_ptr指针指针。

shared_ptr

如果有一种方式,可以记录引用特定内存对象的智能指针数量,当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放它!这就是 shared_ptr 采用的策略!

引用计数的使用

调用use_count函数可以获得当前托管指针的引用计数。

shared_ptr sp1;
shared_ptr sp2(new Person(2));

// 获取智能指针管控的共享指针的数量	use_count():引用计数
cout << "sp1	use_count() = " << sp1.use_count() << endl;  // use_count() = 0
cout << "sp2	use_count() = " << sp2.use_count() << endl << endl; // use_count() = 1

// 共享
sp1 = sp2;

cout << "sp1	use_count() = " << sp1.use_count() << endl; // use_count() = 2
cout << "sp2	use_count() = " << sp2.use_count() << endl << endl; // use_count() = 2

shared_ptr sp3(sp1);
cout << "sp1	use_count() = " << sp1.use_count() << endl; // use_count() = 3
cout << "sp2	use_count() = " << sp2.use_count() << endl; // use_count() = 3
cout << "sp2	use_count() = " << sp3.use_count() << endl << endl;// use_count() = 3

sp1 = sp2; --> sp1和sp2共同托管同一个指针,所以他们的引用计数为2;

shared_ptr< Person > sp3(sp1); --> sp1和sp2和sp3共同托管同一个指针,所以他们的引用计数为3;

构造

shared_ptr< T > sp1; 空的shared_ptr,可以指向类型为T的对象

shared_ptr sp1;
Person *person1 = new Person(1);
sp1.reset(person1);	// 托管person1

shared_ptr< T > sp2(new T()); 定义shared_ptr,同时指向类型为T的对象

shared_ptr sp2(new Person(2));
shared_ptr sp3(sp1);

shared_ptr< T > sp6(NULL, D()); //空的shared_ptr,接受一个D类型的删除器,使用D释放内存

// 仿函数,内存删除
class DestructPerson {
public:
	void operator() (Person *pt) {
		cout << "DestructPerson..." << endl;
		delete pt;
	}
};
/************************************************************/
shared_ptr sp6(NULL, DestructPerson());

初始化

  • 方式一:构造函数

    shared_ptr up1(new int(10));  // int(10) 的引用计数为1
    shared_ptr up2(up1);  // 使用智能指针up1构造up2, 此时int(10) 引用计数为2
    
  • 方式二:使用make_shared 初始化对象,分配内存效率更高(推荐使用)

    make_shared函数的主要功能是在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr; 用法:make_shared<类型>(构造类型对象需要的参数列表);

    shared_ptr up3 = make_shared(2); // 多个参数以逗号','隔开,最多接受十个
    shared_ptr up4 = make_shared("字符串");
    shared_ptr up5 = make_shared(9);
    

    赋值

    shared_ptr up1(new int(10));  // int(10) 的引用计数为1
    shared_ptr up2(new int(11));   // int(11) 的引用计数为1
    up1 = up2;	
    // int(10) 的引用计数减1,计数归零内存释放,up2共享int(11)给up1, int(11)的引用计数为2
    

主动释放对象

shared_ptr up1(new int(10));
up1 = nullptr ;	// int(10) 的引用计数减1,计数归零内存释放 
// 或
up1 = NULL; // 作用同上 

重置

p.reset() ; 将p重置为空指针,所管理对象引用计数 减1

p.reset(p1); 将p重置为p1(的值),p 管控的对象计数减1,p接管对p1指针的管控

p.reset(p1,d); 将p重置为p1(的值),p 管控的对象计数减1并使用d作为删除器

p1是一个指针!(shared_ptr)

交换

p1 和 p2 是智能指针

std::swap(p1,p2); // 交换p1 和p2 管理的对象,原对象的引用计数不变
p1.swap(p2);    // 交换p1 和p2 管理的对象,原对象的引用计数不变

shared_ptr使用陷阱

shared_ptr作为被管控的对象的成员时,小心因循环引用造成无法释放资源!

A类中有B的智能指针;
B类中有A的智能指针;
当他们交叉互相持有对方的管理对象时…

void useTrap() {
	shared_ptr a(new A());
	shared_ptr b(new B());

	// 陷阱用法
	a->setB(b);
	b->setA(a);
	// 此时a和b的引用计数都是2
}

所以在使用shared_ptr智能指针时,要注意避免对象交叉使用智能指针的情况! 否则会导致内存泄露!

week_str

weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。 同时weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。

弱指针不支持 * 和 -> 对指针的访问;

shared_ptr spBoy(new Boy());
shared_ptr spGirl(new Girl());

// 弱指针的使用
weak_ptr wpGirl_1;			// 定义空的弱指针
weak_ptr wpGirl_2(spGirl);	// 使用共享指针构造
wpGirl_1 = spGirl;					// 允许共享指针赋值给弱指针

cout << "spGirl \t use_count = " << spGirl.use_count() << endl;
cout << "wpGirl_1 \t use_count = " << wpGirl_1.use_count() << endl;

	
// 弱指针不支持 * 和 -> 对指针的访问
/*wpGirl_1->setBoyFriend(spBoy);
(*wpGirl_1).setBoyFriend(spBoy);*/

// 在必要的使用可以转换成共享指针
shared_ptr sp_girl;
sp_girl = wpGirl_1.lock(); //在必要的时候可以转换成共享指针 lock();

cout << sp_girl.use_count() << endl;
// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;

expired函数的用法

expired:判断当前weak_ptr智能指针是否还有托管的对象,有则返回false,无则返回true

如果返回true,等价于 use_count() == 0,即已经没有托管的对象了;当然,可能还有析构函数进行释放内存,但此对象的析构已经临近(或可能已发生)。

#include 
#include 

std::weak_ptr gw;

void f() {
	// expired:判断当前智能指针是否还有托管的对象,有则返回false,无则返回true
	if (!gw.expired()) {
		std::cout << "gw is valid\n";	// 有效的,还有托管的指针
	} else {
		std::cout << "gw is expired\n";	// 过期的,没有托管的指针
	}
}

int main() {
	{
		auto sp = std::make_shared(42);
		gw = sp;

		f();
	}

	// 当{ }体中的指针生命周期结束后,再来判断其是否还有托管的指针
	f();

	return 0;
}

在 { } 中,gw的生命周期还在,他还在托管着make_shared赋值的指针,所以调用f()函数时打印"gw is valid\n";

当执行完 { } 后,gw的生命周期已经结束,已经调用析构函数释放make_shared指针内存,gw已经没有在托管任何指针了,调用expired()函数返回true,所以打印"gw is expired\n";

智能指针的使用陷阱

  • 不要把一个原生指针给多个智能指针管理
  • 禁止用任何类型智能指针get 函数返回的指针去初始化另外一个智能指针
  • 在调用u.release()时是不会释放u所指的内存的,这时返回值就是对这块内存的唯一索引,如果没有使用这个返回值释放内存或是保存起来,这块内存就泄漏了
  • 禁止delete 智能指针get 函数返回的指针
  • 如果主动释放掉get 函数获得的指针,那么智能 指针内部的指针就变成野指针了,析构时造成重复释放,带来严重后果!
  • 禁止用任何类型智能指针get 函数返回的指针去初始化另外一个智能指针

你可能感兴趣的:(杂七杂八,c++,智能指针)