程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++11中shared_ptr的使用

C++11中shared_ptr的使用

編輯:關於C++

在C++中,動態內存的管理是通過一對運算符來完成的:new,在動態內存中為對象分配空間並返回一個指向該對象的指針,可以選擇對對象進行初始化;delete,接受一個動態對象的指針,銷毀該對象,並釋放與之關聯的內存。

動態內存的使用很容易出問題,因為確保在正確的時間釋放內存是極其困難的。有時會忘記釋放內存,在這種情況下會產生內存洩露;有時在尚有指針引用內存的情況下就釋放了它,在這種情況下就會產生引用非法內存的指針。

為了更容易(同時也更安全)地使用動態內存,C++11標准庫提供了兩種智能指針(smart pointer)類型來管理動態對象。智能指針的行為類似常規指針,重要的區別是它負責自動釋放所指的對象。C++11標准庫提供的這兩種智能指針的區別在於管理底層指針的方式:shared_ptr允許多個指針指向同一個對象;unique_ptr則"獨占"所指向的對象。C++11標准庫還定義了一個名為weak_ptr的輔助類,它是一種弱引用,指向shared_ptr所管理的對象。這三種類型都定義在memory頭文件中。智能指針是模板類而不是指針。類似vector,智能指針也是模板,當創建一個智能指針時,必須提供額外的信息即指針可以指向的類型。默認初始化的智能指針中保存著一個空指針。智能指針的使用方式與普通指針類似。解引用一個智能指針返回它指向的對象。如果在一個條件判斷中使用智能指針,效果就是檢測它是否為空。

std::shared_ptris a smart pointer that retains shared ownership of an object through a pointer. Several shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of the following happens: (1)、the last remaining shared_ptr owning the object is destroyed; (2)、the last remaining shared_ptr owning the object is assigned another pointer via operator= or reset().

A shared_ptr can share ownership of an object while storing a pointer to another object. This feature can be used to point to member objects while owning the object they belong to. The stored pointer is the one accessed by get(), the dereference and the comparison operators. The managed pointer is the one passed to the deleter when use count reaches zero.

A shared_ptr may also own no objects, in which case it is called empty (an empty shared_ptr may have a non-null stored pointer if the aliasing constructor was used to create it).

All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of shared_ptr without additional synchronization even if these instances are copies and share ownership of the same object. If multiple threads of execution access the same shared_ptr without synchronization and any of those accesses uses a non-const member function of shared_ptr then a data race will occur; the shared_ptr overloads of atomic functions can be used to prevent the data race.

The ownership of an object can only be shared with another shared_ptr by copy constructing or copy assigning its value to another shared_ptr. Constructing a new shared_ptr using the raw underlying pointer owned by another shared_ptr leads to undefined behavior.

The shared_ptr type is a smart pointer in the C++ standard library that is designed for scenarios in which more than one owner might have to manage the lifetime of the object in memory. After you initialize a shared_ptr you can copy it, pass it by value in function arguments, and assign it to other shared_ptr instances. All the instances point to the same object, and share access to one "control block" that increments and decrements the reference count whenever a new shared_ptr is added, goes out of scope, or is reset. When the reference count reaches zero, the control block deletes the memory resource and itself.

Whenever possible, use the make_shared () function to create a shared_ptr when the memory resource is created for the first time. make_shared is exception-safe. It uses the same call to allocate the memory for the control block and the resource, and thereby reduces the construction overhead. If you do not use make_shared, then you have to use an explicit new expression to create the object before you pass it to the shared_ptr constructor.

In order to hide the operator new and to provide an optimization while allocating the object to be shared, the variadic template function make_shared was created. It is a template function that performs three tasks:

(1)、Allocates contiguous memory for the object and for the reference counter. This makes the creation and destruction of objects faster because only one allocation and deallocation will be needed when creating the object to be shared and its reference counter.

(2)、Invokes to the constructor of the class being instantiated forwarding the arguments used when this function was invoked.

(3)、Returns a shared_ptr to the newly created object.

make_sharedis a variadic template function that receives as arguments, the arguments that the constructor of class T needs.

智能指針實質就是重載了->和*操作符的類,由類來實現對內存的管理,確保即使有異常產生,也可以通過智能指針類的析構函數完成內存的釋放。

shared_ptr的類型轉換不能使用一般的static_cast,這種方式進行的轉換會導致轉換後的指針無法再被shared_ptr對象正確的管理。應該使用專門用於shared_ptr類型轉換的 static_pointer_cast() , const_pointer_cast() 和dynamic_pointer_cast()。

使用shared_ptr避免了手動使用delete來釋放由new申請的資源,標准庫也引入了make_shared函數來創建一個shared_ptr對象,使用shared_ptr和make_shared,你的代碼裡就可以使new和delete消失,同時又不必擔心內存的洩露。shared_ptr是一個模板類。

C++開發處理內存洩漏最有效的辦法就是使用智能指針,使用智能指針就不會擔心內存洩露的問題了,因為智能指針可以自動刪除分配的內存。

智能指針是指向動態分配(堆)對象指針,用於生存期控制,能夠確保自動正確的銷毀動態分配的對象,防止內存洩露。它的一種通用實現技術是使用引用計數。每次使用它,內部的引用計數加1,每次析構一次,內部引用計數減1,減為0時,刪除所指向的堆內存。

每一個shared_ptr的拷貝都指向相同的內存。在最後一個shared_ptr析構的時候, 內存才會被釋放。

可以通過構造函數、賦值函數或者make_shared函數初始化智能指針。

shared_ptr基於”引用計數”模型實現,多個shared_ptr可指向同一個動態對象,並維護一個共享的引用計數器,記錄了引用同一對象的shared_ptr實例的數量。當最後一個指向動態對象的shared_ptr銷毀時,會自動銷毀其所指對象(通過delete操作符)。

shared_ptr的默認能力是管理動態內存,但支持自定義的Deleter以實現個性化的資源釋放動作。

最安全的分配和使用動態內存的方法是調用一個名為make_shared的標准庫函數。此函數在動態內存中分配一個對象並初始化它,返回指向此對象的shared_ptr。當要用make_shared時,必須指定想要創建的對象的類型,定義方式與模板類相同。在函數名之後跟一個尖括號,在其中給出類型。例如,調用make_shared時傳遞的參數必須與string的某個構造函數相匹配。如果不傳遞任何參數,對象就會進行值初始化。

通常用auto定義一個對象來保存make_shared的結果。

當進行拷貝或賦值操作時,每個shared_ptr都會記錄有多少個其它shared_ptr指向相同的對象。

可以認為每個shared_ptr都有一個關聯的計數器,通常稱其為引用計數(reference count)。無論何時拷貝一個shared_ptr,計數器都會遞增。例如,當用一個shared_ptr初始化另一個shared_ptr,或將它作為參數傳遞給一個函數以及作為函數的返回值時,它所關聯的計數器就會遞增。當給shared_ptr賦予一個新值或是shared_ptr被銷毀(例如一個局部的shared_ptr離開其作用域)時,計數器就會遞減。一旦一個shared_ptr的計數器變為0,它就會自動釋放自己所管理的對象。

當指向一個對象的最後一個shared_ptr被銷毀時,shared_ptr類會自動銷毀此對象。它是通過另一個特殊的成員函數析構函數(destructor)來完成銷毀工作的。類似於構造函數,每個類都有一個析構函數。就像構造函數控制初始化一樣,析構函數控制此類型的對象銷毀時做什麼操作。shared_ptr的析構函數會遞減它所指向的對象的引用計數。如果引用計數變為0,shared_ptr的析構函數就會銷毀對象,並釋放它占用的內存。

如果將shared_ptr存放於一個容器中,而後不再需要全部元素,而只使用其中一部分,要記得用erase刪除不再需要的那些元素。

使用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支持的操作(來源於C++ Primer Fifth Edition 中文版):

\

下面是從其他文章中copy的測試代碼,詳細內容介紹可以參考對應的reference:

 

#include "shared_ptr.hpp"
#include 
#include  // shared_ptr
#include 
#include 
#include 

//////////////////////////////////////////////////////
// reference: http://en.cppreference.com/w/cpp/memory/shared_ptr
struct Base
{
	Base() { std::cout << "  Base::Base()\n"; }
	// Note: non-virtual destructor is OK here
	~Base() { std::cout << "  Base::~Base()\n"; }
};

struct Derived : public Base
{
	Derived() { std::cout << "  Derived::Derived()\n"; }
	~Derived() { std::cout << "  Derived::~Derived()\n"; }
};

void thr(std::shared_ptr p)
{
	std::this_thread::sleep_for(std::chrono::seconds(1));
	std::shared_ptr lp = p; // thread-safe, even though the shared use_count is incremented
	{
		static std::mutex io_mutex;
		std::lock_guard lk(io_mutex);
		std::cout << "local pointer in a thread:\n"
			<< "  lp.get() = " << lp.get()
			<< ", lp.use_count() = " << lp.use_count() << '\n';
	}
}

int test_shared_ptr1()
{
	std::shared_ptr p = std::make_shared();

	std::cout << "Created a shared Derived (as a pointer to Base)\n"
		<< "  p.get() = " << p.get()
		<< ", p.use_count() = " << p.use_count() << '\n';
	std::thread t1(thr, p), t2(thr, p), t3(thr, p);
	p.reset(); // release ownership from main
	std::cout << "Shared ownership between 3 threads and released\n"
		<< "ownership from main:\n"
		<< "  p.get() = " << p.get()
		<< ", p.use_count() = " << p.use_count() << '\n';
	t1.join(); t2.join(); t3.join();
	std::cout << "All threads completed, the last one deleted Derived\n";

	return 0;
}

///////////////////////////////////////////////
// reference: http://www.cplusplus.com/reference/memory/shared_ptr/shared_ptr/
int test_shared_ptr2()
{
	struct C { int* data; };

	// shared_ptr constructor example
	std::shared_ptr p1;
	std::shared_ptr p2(nullptr);
	std::shared_ptr p3(new int);
	std::shared_ptr p4(new int, std::default_delete());
	std::shared_ptr p5(new int, [](int* p){delete p; }, std::allocator());
	std::shared_ptr p6(p5);
	std::shared_ptr p7(std::move(p6));
	std::shared_ptr p8(std::unique_ptr(new int));
	std::shared_ptr obj(new C);
	std::shared_ptr p9(obj, obj->data);

	std::cout << "use_count:\n";
	std::cout << "p1: " << p1.use_count() << '\n'; // 0
	std::cout << "p2: " << p2.use_count() << '\n'; // 0
	std::cout << "p3: " << p3.use_count() << '\n'; // 1
	std::cout << "p4: " << p4.use_count() << '\n'; // 1
	std::cout << "p5: " << p5.use_count() << '\n'; // 2
	std::cout << "p6: " << p6.use_count() << '\n'; // 0
	std::cout << "p7: " << p7.use_count() << '\n'; // 2
	std::cout << "p8: " << p8.use_count() << '\n'; // 1
	std::cout << "p9: " << p9.use_count() << '\n'; // 2

	return 0;
}

//////////////////////////////////////////////////////////
// reference: https://oopscenities.net/2013/10/06/smart-pointers-part-4-shared_ptr/
class Integer
{
	int n;
public:
	Integer(int n) : n(n) { }
	~Integer() { printf("Deleting %d\n", n); }
	int get() const { return n; }
};

int test_shared_ptr3()
{
	auto a = std::make_shared(10);
	auto b = std::make_shared(20);
	auto c = a;
	auto d = std::make_shared(30);
	auto e = b;
	a = d;
	b = std::make_shared(40);
	auto f = c;
	b = f;

	printf("%d\n", a->get());
	printf("%d\n", b->get());
	printf("%d\n", c->get());
	printf("%d\n", d->get());
	printf("%d\n", e->get());
	printf("%d\n", f->get());

	return 0;
}

//////////////////////////////////////////////
// reference: http://www.linux-magazin.de/Ausgaben/2013/04/C-11
struct MyInt{
	MyInt(int v) :val(v){
		std::cout << "  Hello: " << val << std::endl;
	}
	~MyInt(){
		std::cout << "  Good Bye: " << val << std::endl;
	}
	int val;
};

int test_shared_ptr4()
{
	std::shared_ptr sharPtr(new MyInt(1998));
	std::cout << "    My value: " << sharPtr->val << std::endl;
	std::cout << "sharedPtr.use_count(): " << sharPtr.use_count() << std::endl;

	{
		std::shared_ptr locSharPtr(sharPtr);
		std::cout << "locSharPtr.use_count(): " << locSharPtr.use_count() << std::endl;
	}
	std::cout << "sharPtr.use_count(): " << sharPtr.use_count() << std::endl;

	std::shared_ptr globSharPtr = sharPtr;
	std::cout << "sharPtr.use_count(): " << sharPtr.use_count() << std::endl;
	globSharPtr.reset();
	std::cout << "sharPtr.use_count(): " << sharPtr.use_count() << std::endl;

	sharPtr = std::shared_ptr(new MyInt(2011));

	return 0;
}

////////////////////////////////////////////////////////
// reference: http://www.linux-magazin.de/Ausgaben/2013/04/C-11
template 
struct Deleter{
	void operator()(T *ptr){
		++Deleter::count;
		delete ptr;
	}
	static int count;
};

template 
int Deleter::count = 0;

typedef Deleter IntDeleter;
typedef Deleter DoubleDeleter;
typedef Deleter MyIntDeleter;

int test_shared_ptr5()
{
	{
		std::shared_ptr sharedPtr1(new int(1998), IntDeleter());
		std::shared_ptr sharedPtr2(new int(2011), IntDeleter());
		std::shared_ptr sharedPtr3(new double(3.17), DoubleDeleter());
		std::shared_ptr sharedPtr4(new MyInt(2017), MyIntDeleter());
	}

	std::cout << "Deleted " << IntDeleter().count << " int values." << std::endl;
	std::cout << "Deleted " << DoubleDeleter().count << " double value." << std::endl;
	std::cout << "Deleted " << MyIntDeleter().count << " MyInt value." << std::endl;

	return 0;
}
GitHub:https://github.com/fengbingchun/Messy_Test
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved