【C++】C++11——智能指针、内存泄漏、智能指针的使用和原理、RAII、auto_ptr、unique_ptr、shared_ptr、weak_ptr

文章目录

  • C++11
    • 7.智能指针
      • 7.1内存泄漏
      • 7.2智能指针的概念
      • 7.3智能指针的使用
        • 7.3.1 auto_ptr
        • 7.3.2 unique_ptr
        • 7.3.3 shared_ptr
        • 7.3.4 weak_ptr

C++11

在这里插入图片描述

7.智能指针

7.1内存泄漏

  什么是内存泄漏:

  内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

  内存泄漏通常由于程序在设计上的缺陷或错误,例如动态分配内存后,未在合适的时间或无法正确释放该段内存,而导致的。内存泄漏通常需要程序员通过分析程序源代码来识别和修复。

  内存泄漏的危害:

  在某些情况下,内存泄漏可能并不严重,或者能够被常规手段检测出来。然而,如果内存泄漏持续存在并导致大量可用内存被分配掉,可能会导致计算机性能显著下降,甚至导致全部或部分设备停止正常工作,或者应用程序崩溃。如果长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

  
  内存泄漏的示例:

void MemoryLeaks()
{
   // 1.内存申请了忘记释放
  int* p1 = (int*)malloc(sizeof(int));
  int* p2 = new int;
  
  // 2.异常安全问题
  int* p3 = new int[10];
  
  Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
  
  delete[] p3;
}

  
  如何避免内存泄漏:

  (1)合理使用动态内存:尽量避免使用动态内存分配,因为使用动态内存分配后需要手动释放内存,否则会造成内存泄漏。如果必须使用动态内存分配,应该确保在使用完毕后及时释放内存。

  (2)及时释放内存:在使用完动态分配的内存后,必须及时释放它们,以避免内存泄漏。在C++中,可以在析构函数中释放内存;在C中,可以使用free()函数来释放内存。

  (3)使用智能指针:智能指针是一种能够自动管理内存的机制,可以避免由于忘记释放内存而导致的内存泄漏。 C++11标准库提供了多种类型的智能指针,如unique_ptr、shared_ptr和weak_ptr,可以根据具体情况选择合适的智能指针类型来管理内存。

  (4)尽量避免循环引用:如果两个对象之间存在相互引用关系,并且没有任何其他对象或机制来打破循环引用,那么就会导致内存泄漏。因此,在设计程序时应该尽可能避免循环引用,或者使用其他机制来打破循环引用。

  (5)使用垃圾回收机制:一些编程语言提供了垃圾回收机制,可以自动检测并回收不再使用的内存。这些语言包括Java、Python和JavaScript等。如果使用这些语言,可以充分利用它们的垃圾回收机制来避免内存泄漏。

  (6)定期检查程序:定期检查程序是否存在内存泄漏是一种有效的手段。可以使用一些工具来检测程序中的内存泄漏, 例如Valgrind和AddressSanitizer等。这些工具可以帮助发现程序中的内存泄漏问题,并提供修复建议。

            

7.2智能指针的概念

  智能指针是一种C++中的对象,它用于管理动态分配的内存,并自动释放内存以避免内存泄漏。智能指针的工作原理是,当智能指针超出其作用域或被销毁时,它会自动释放它所管理的内存。

  智能指针的常见类型包括unique_ptr、shared_ptr和weak_ptr。 unique_ptr表示只有一个指针指向动态分配的内存,当unique_ptr被销毁时,它所管理的内存会被自动释放。shared_ptr表示多个指针可以指向同一块动态分配的内存,当最后一个指向该内存的shared_ptr被销毁时,它所管理的内存会被自动释放。weak_ptr是shared_ptr的一个别名,它不持有动态分配的内存的任何所有权,而是用于与其他智能指针一起使用,以避免循环引用。

  下面是一个简单的unique_ptr使用的例子:

  在这个例子中,我们首先定义了一个名为MyStruct的结构体,它有一个整数值。接下来,在main函数中,我们使用std::unique_ptr< MyStruct >类型定义了一个智能指针对象ptr,并使用new操作符分配了一个新的MyStruct对象,将它的指针赋给了ptr。我们可以通过ptr->value来访问MyStruct对象的值。最后,当main函数返回时,ptr将自动释放它所管理的内存,避免了内存泄漏。

#include   
#include   
  
struct MyStruct {  
    int value;  
    MyStruct(int v) : value(v) {}  
};  
  
int main() {  
    std::unique_ptr<MyStruct> ptr(new MyStruct(10));  
    std::cout << "Value: " << ptr->value << std::endl;  
    return 0;  
}

            

7.3智能指针的使用

   RAII:

  RAII是资源获取就是初始化的简称,是C++语言的一种管理资源、避免泄漏的惯用法。

  RAII的核心思想是,定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理。这样可以保证资源的正确初始化和释放,防止内存泄漏。

  利用C++构造的对象最终会被销毁的原则,当对象被销毁时,会自动调用其析构函数,释放对象生命期内控制的资源。

  RAII有两个好处:

  (1)不需要显式地释放资源;

  (2)采用这种方式,对象所需的资源在其生命期内始终保持有效。

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr 
{
public:
	SmartPtr(T* ptr = nullptr)
			: _ptr(ptr)
		{}

	~SmartPtr()
	{
		if(_ptr)
		delete _ptr;
	}

private:
	T* _ptr;
};

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
	throw invalid_argument("除0错误");
	return a / b;
}

void Func()
{
	ShardPtr<int> sp1(new int);
	ShardPtr<int> sp2(new int);
	cout << div() << endl;
}

int main()
{
	try 
	{
		Func();
	}
	catch(const exception& e)
	{
		cout<<e.what()<<endl;
	}
	return 0;
}

  

  上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:AutoPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。

  
  总结一下智能指针的原理:

  (1)RAII特性

  (2)重载operator*和opertaor->,具有像指针一样的行为

template<class T>
class SmartPtr 
{
public:
	SmartPtr(T* ptr = nullptr)
    	 : _ptr(ptr)
	 {}
	 
	~SmartPtr()
 	{
     	if(_ptr)
         	delete _ptr;
 	}
 	
	T& operator*() {return *_ptr;}

	T* operator->() {return _ptr;}

private:
	T* _ptr;
};

struct Date
{
	int _year;
	int _month;
	int _day;
};

int main()
{
	SmartPtr<int> sp1(new int);
	*sp1 = 10
	cout<<*sp1<<endl;
	SmartPtr<int> sparray(new Date);
	// 需要注意的是这里应该是sparray.operator->()->_year = 2018;
	// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->
	sparray->_year = 2018;
	sparray->_month = 1;
	sparray->_day = 1;
}

  

7.3.1 auto_ptr

std::auto_ptr

  C++98版本的库中就提供了auto_ptr的智能指针。

  注意:auto_ptr被认为是一个失败的设计:

  (1)所有权的转移不清晰:auto_ptr在构造时获取对象的所有权,但可以通过赋值操作将所有权转移给另一个auto_ptr。这样会造成原来的auto_ptr指针悬空,程序直接崩溃。

  (2)无法在STL容器中使用:auto_ptr的拷贝构造函数会进行所有权的转移,这使得它难以被用在STL容器中。因为在容器中添加元素时,会涉及到拷贝操作,这可能会导致所有权的转移,从而使得容器中的元素所指向的对象被释放,引起程序错误。

  (3)nullptr的问题:auto_ptr无法安全地处理nullptr。如果你试图让一个auto_ptr指向一个nullptr,它会在析构时释放该指针,即使该指针从未被分配过内存。

  (4)不支持自定义删除器:auto_ptr只能使用默认的delete操作符来释放内存,无法使用自定义的删除器。这限制了auto_ptr的灵活性。基于以上原因,现代C++编程中更推荐使用unique_ptr、shared_ptr和weak_ptr来替代auto_ptr。

  auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份bit::auto_ptr来了解它的原理:

template<class T>
class auto_ptr
{
public:
	auto_ptr(T* ptr)
		:_ptr(ptr)
	{}

	~auto_ptr()
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

	//管理权转移
	auto_ptr(auto_ptr<T>& ap)
		:_ptr(ap._ptr)
	{
		ap._ptr = nullptr;
	}

private:
	T* _ptr;
};

  

7.3.2 unique_ptr

std::unique_ptr

  unique_ptr是C++11标准库中的一种智能指针,它持有对对象的独有权,即两个unique_ptr不能指向同一个对象,不能进行复制操作,只能进行移动操作。 当unique_ptr超出其作用域或被销毁时,它所管理的内存会被自动释放,有效避免了内存泄漏问题。unique_ptr是一种方便、安全、自动管理内存的机制,可以有效避免内存泄漏问题。

  unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份unique_ptr来了解它的原理:

template<class T>
class unique_ptr
{
public:
	unique_ptr(T* ptr)
		:_ptr(ptr)
	{}

	~unique_ptr()
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

	//禁止拷贝
	unique_ptr(unique_ptr<T>& ap) = delete;
	unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;

private:
	T* _ptr;
};

  

7.3.3 shared_ptr

std::shared_ptr

  shared_ptr是一种C++中的智能指针,它可以记录有多少个shared_ptrs指向一个对象。当最后一个这样的指针被销毁时,也就是最后一个指向对象的shared_ptr被销毁时,该对象会被自动删除,这在非环形数据结构中防止资源泄露很有帮助。

  它与unique_ptr不同,shared_ptr允许多个指针指向同一个对象,并共同拥有该对象的所有权。当最后一个shared_ptr被销毁时,它所指向的对象也会被自动删除。

  
  shared_ptr的实现原理:

  shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。

  如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源; 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

  shared_ptr的实现原理:使用了引用计数,下面简化模拟实现了一份shared_ptr来了解它的原理:

template<class T>
class shared_ptr
{
public:
	// RAII
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}

	~shared_ptr()
	{
		if (--(*_pcount) == 0)
		{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
			delete _pcount;
		}
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

	shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _pcount(sp._pcount)
	{
		++(*_pcount);
	}

	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		if (_ptr == sp._ptr)
			return *this;

		if (--(*_pcount) == 0)
		{
			delete _ptr;
			delete _pcount;
		}

		_ptr = sp._ptr;
		_pcount = sp._pcount;
		++(*_pcount);

		return *this;
	}

	int use_count() const
	{
		return *_pcount;
	}

	T* get() const
	{
		return _ptr;
	}

private:
	T* _ptr;
	int* _pcount;
};

  

7.3.4 weak_ptr

std::weak_ptr

  weak_ptr是C++中为了配合shared_ptr解决循环引用问题而引入的一种智能指针。

  weak_ptr可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起shared_ptr引用记数的增加或减少。 weak_ptr的一个重要用途是通过lock获得this指针的shared_ptr,使对象自己能够生产shared_ptr来管理自己。总的来说,weak_ptr主要用于配合shared_ptr,以避免内存泄漏。

  下面简化模拟实现了一份weak_ptr来了解它的原理:

template<class T>
class weak_ptr
{
public:
	weak_ptr()
		:_ptr(nullptr)
	{}

	weak_ptr(const shared_ptr<T>& sp)
		:_ptr(sp.get())
	{}

	weak_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		_ptr = sp.get();
		return *this;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

  

你可能感兴趣的:(C++,c++)