unique_ptr
1.概念
unique_ptr形如其名,與所指對象的內存緊密地綁定,不能與其他的unique_ptr類型的指針對象共享所指向對象的內存。
在cplusplus.com中,unique_ptr聲明如下:
// non-specialized
template <class T, class D = default_delete<T>> class unique_ptr;
// array specialization
template <class T, class D> class unique_ptr<T[],D>;
是一個模版類,T指得是指向內存的類型,D指得是deleter類型,默認為default_deleter。
請看如下例子:
int main(int argc, char* argv[]) {
std::unique_ptr<int> u1(new int(1));
std::cout << "u1 value : " << *u1 << '\n' << " addredd : " << u1.get() << std::endl;
std::unique_ptr<int> u2 = u1; //編譯出錯
return 0;
}
編譯結果如下:
從編譯log來看,use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]’
,具體原因是unique_ptr不允許與其他對象共享所指向對象的內存,已經刪除了拷貝構造函數,無法進行拷貝操作。
將上述代碼改為如下形式編譯成功:
int main(int argc, char* argv[]) {
std::unique_ptr<int> u1(new int(1));
std::cout << "u1 value : " << *u1 << '\n' << " addredd : " << u1.get() << std::endl;
std::unique_ptr<int> u2 = move(u1);
std::cout << "u2 value : " << *u2 << '\n' << " addredd : " << u2.get() << std::endl;
return 0;
}
執行結果如下:
從結果可以看出u2所指向的值為1,u2所指向的地址與u1所指向的地址相同(通過get成員函數可以獲取所指向的地址)。這是由於使用了move操作,u1把內存的所有權釋放,u2獲取到內存的所有權,所以u2所指向的地址和u1所指向的地址相同。此時如果再次打印u1的值和所指向的內存地址,代碼如下:
int main(int argc, char* argv[]) {
std::unique_ptr<int> u1(new int(1));
std::cout << "u1 value : " << *u1 << '\n' << " addredd : " << u1.get() << std::endl;
std::unique_ptr<int> u2 = move(u1);
std::cout << "u2 value : " << *u2 << '\n' << " addredd : " << u2.get() << std::endl;
std::cout << "u1 value : " << *u1 << '\n' << " addredd : " << u1.get() << std::endl;
return 0;
}
執行結果如下:
出現Segmentation fault的原因是u1此時以及把所指內存的所有權轉給u2,但還是打印了u1所指的值和地址。在此我就不進行core file分析了。
2.用法
1.構造函數
unique_ptr一共有8個構造函數,分別是:
描述 | 函數原型 |
---|---|
1.默認構造函數 | constexpr unique_ptr() noexcept; |
2.初始化為空指針 | constexpr unique_ptr (nullptr_t) noexcept : unique_ptr() {} |
3.初始化為非空指針 | explicit unique_ptr (pointer p) noexcept; |
4.初始化為非空指針+左值deleter | unique_ptr (pointer p,typename |
5.初始化為非空指針+右值deleter | unique_ptr (pointer p,typename remove_reference
|
6.移動構造函數 | unique_ptr (unique_ptr&& x) noexcept; |
7.非默認deleter的移動構造 | template <class U, class E> unique_ptr (unique_ptr<U,E>&& x) noexcept; |
8.從auto_ptr類型移動(C++17中移除掉這種用法了) | template
|
9.拷貝構造函數(已刪除,不可使用 | unique_ptr (const unique_ptr&) = delete; |
下面我們分別測試一下每種構造函數的用法:
#include <memory>
#include <iostream>
using namespace std;
class smart_point_class {
public:
smart_point_class(int i) : _i(i) {
cout << "Default construct..." << _i << endl;
}
~smart_point_class() {
cout << "Destroct..." << _i << endl;
}
inline int get_value() {
return _i;
}
private:
smart_point_class(const smart_point_class&) = delete;
smart_point_class(smart_point_class&&) = delete;
private:
int _i;
};
class deleter {
public:
deleter() = default;
deleter(const deleter& other) {
cout << "Copy deleter." << endl;
}
deleter(deleter&& other) {
cout << "Move deleter." << endl;
}
deleter& operator=(const deleter&) {
cout << "Copy assign deleter." << endl;
return *this;
}
deleter& operator=(deleter&&) {
cout << "Move assign deleter." << endl;
return *this;
}
void operator() (smart_point_class* spc) {
cout << "Start of deleter function : " << spc->get_value() << endl;
delete spc;
spc = nullptr;
cout << "End of deleter function." << endl;
}
};
void test_construct_fun() {
cout << "C++ unique_ptr test function :" << endl;
cout << "----------------------------------------------------" << endl;
cout << "u1" << endl;
unique_ptr<smart_point_class> u1;
cout << "u1 : " << (u1? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u2" << endl;
unique_ptr<smart_point_class> u2(nullptr);
cout << "u2 : " << (u2? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u3" << endl;
unique_ptr<smart_point_class> u3(new smart_point_class(3));
cout << "u3 : " << (u3? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u4" << endl;
deleter d;
unique_ptr<smart_point_class, decltype(d)> u4(new smart_point_class(4), d);
cout << "u4 : " << (u4? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u5" << endl;
unique_ptr<smart_point_class, deleter> u5(new smart_point_class(5), deleter());
cout << "u5 : " << (u5? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u6" << endl;
unique_ptr<smart_point_class> u6 = move(u3);
cout << "u6 : " << (u6? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u7" << endl;
unique_ptr<smart_point_class, deleter> u7 = move(u5);
cout << "u7 : " << (u7? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u8" << endl;
unique_ptr<smart_point_class> u8 (auto_ptr<smart_point_class>(new smart_point_class(8)));
cout << "u8 : " << (u8? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "Test finish..." << endl;
cout << "u1 : " << (u1? "not null" : "null") << '\n';
cout << "u2 : " << (u2? "not null" : "null") << '\n';
cout << "u3 : " << (u3? "not null" : "null") << '\n';
cout << "u4 : " << (u4? "not null" : "null") << '\n';
cout << "u5 : " << (u5? "not null" : "null") << '\n';
cout << "u6 : " << (u6? "not null" : "null") << '\n';
cout << "u7 : " << (u7? "not null" : "null") << '\n';
cout << "u8 : " << (u8? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
}
int main(int argc, char* argv[]) {
test_construct_fun();
return 0;
}
編譯結果如下:
會發現有個警告,是因為在C++11中不再建議使用auto_ptr了,但既然構造函數中有這個用法,我們就在這里測試一下(在C++17中已經移除掉這種用法了)。
這段代碼執行結果如下:
現在我們來分析一下執行結果:
- u1使用了默認構造函數,所指向內容為空;
- u2使用了初始化為空指針的構造函數,所指向內容為空;
- u3使用了初始化為非空指針的構造函數,所指向內容不為空;
- u4使用了初始化為非空指針+左值deleter的構造函數,會線構造一個deleter,然后拷貝這個deleter,所指向內容不為空;
- u5使用了初始化為非空指針+右值deleter的構造函數,會線構造一個deleter,然后移動這個deleter,所指向內容不為空;
- u6使用了移動構造函數,u3將所有權轉到了u6,u6不為空,但u3變為空;
- u7使用了自定義deleter類型的移動構造函數,u5將所有權轉到了u7,u7不為空,但u5變為空;
- u8使用了從auto_ptr類型移動的構造函數,不為空,但C++17已經移除了這種用法,不再建議使用;
- 在函數體內所有語句執行完畢之后,釋放資源。其中使用默認deleter的會調用smart_pointer_class的析構函數,不使用默認deleter的會調用deleter函數/仿函數。
2.析構函數
沒有需要特殊說明的。
3.賦值重載操作
unique_ptr一共有3個賦值重載操作,分別是:
描述 | 函數原型 |
---|---|
1.移動賦值 | unique_ptr& operator= (unique_ptr&& x) noexcept; |
2.賦值為空 | unique_ptr& operator= (nullptr_t) noexcept; |
3.賦值非默認deleter的unique_ptr | template <class U, class E> unique_ptr& operator= (unique_ptr<U,E>&& x) noexcept; |
4.拷貝賦值函數(已刪除,不可使用) | unique_ptr& operator= (const unique_ptr&) = delete; |
下面我們分別測試一下每種賦值函數的用法:
#include <memory>
#include <iostream>
using namespace std;
class smart_point_class {
public:
smart_point_class(int i) : _i(i) {
cout << "Default construct..." << _i << endl;
}
~smart_point_class() {
cout << "Destroct..." << _i << endl;
}
inline int get_value() {
return _i;
}
private:
smart_point_class(const smart_point_class&) = delete;
smart_point_class(smart_point_class&&) = delete;
private:
int _i;
};
class deleter {
public:
deleter() = default;
deleter(const deleter& other) {
cout << "Copy deleter." << endl;
}
deleter(deleter&& other) {
cout << "Move deleter." << endl;
}
deleter& operator=(const deleter&) {
cout << "Copy assign deleter." << endl;
return *this;
}
deleter& operator=(deleter&&) {
cout << "Move assign deleter." << endl;
return *this;
}
void operator() (smart_point_class* spc) {
cout << "Start of deleter function : " << spc->get_value() << endl;
delete spc;
spc = nullptr;
cout << "End of deleter function." << endl;
}
};
void test_assign_fun() {
cout << "C++ unique_ptr assign test :" << endl;
cout << "u_base" << endl;
unique_ptr<smart_point_class> u_base(new smart_point_class(1));
cout << "u_base value : " << u_base->get_value() << '\n' << " addredd : " << u_base.get() << endl;
cout << "----------------------------------------------------" << endl;
cout << "u1" << endl;
unique_ptr<smart_point_class> u1;
u1 = move(u_base);
cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
cout << "----------------------------------------------------" << endl;
unique_ptr<smart_point_class> u2(new smart_point_class(2));
u2 = nullptr;
cout << "----------------------------------------------------" << endl;
unique_ptr<smart_point_class, deleter> u4;
u4 = unique_ptr<smart_point_class, deleter>(new smart_point_class(3));
cout << "----------------------------------------------------" << endl;
cout << "Test finish..." << endl;
}
int main(int argc, char* argv[]) {
test_assign_fun();
return 0;
}
上述代碼執行結果如下:
現在我們來分析一下執行結果:
- u1從u_base處獲取所有權;
- u2使用了初始化為非空指針的構造函數,所指向內容不為空,然后賦值為空,
u2 = nullptr
,等同於u2.reset()
,(在后面會具體說到reset的使用方法),在此進行析構操作; - u3首先指向空,然后用右值賦值,調用了unique_ptr的移動賦值函數;
- 所有語句執行完成之后,釋放資源。u3會調用deleter函數/仿函數,u2在之前已經釋放,最后釋放u1,用默認的deleter。
4.get()
get函數會返回存儲的指針。如果由unique_ptr不為空,則存儲的指針指向由unique_ptr管理的對象,否則指向nullptr。需要注意的是調用這個函數並不會釋放指針的所有權(它仍然負責刪除管理的數據)。因此,此函數的返回值不用於構建新的指針。如果要獲取存儲的指針並釋放所有權,調用unique_ptr::release()。
get函數使用方法如下:
int main(int argc, char* argv[]) {
int *i = new int(1);
cout << "i : " << i << endl;
unique_ptr<int> u(i);
cout << "u value : " << *u << '\n' << "u address : " << u.get() << endl;
return 0;
}
該段代碼執行結果如下:
從圖中看出可以正確打印出地址。
5.get_deleter()
get_deleter函數會返回deleter。deleter是一個可調用的對象。使用成員類型指針的單個參數對該對象的函數調用將刪除托管對象,並在unique_ptr本身被銷毀、分配了一個新值或在非空時重置時自動調用。unique_ptr模板使用的默認deleter類型是default_delete,一個無狀態類。
get_deleter使用例子:
int main(int agrc, char* argv[]) {
// smart_point_class和deleter在之前的代碼中有定義
unique_ptr<smart_point_class, deleter> u(new smart_point_class(1), deleter());
smart_point_class* p(new smart_point_class(2));
cout << "----------------------------------------------------" << endl;
u.get_deleter()(p);
cout << "----------------------------------------------------" << endl;
}
上述代碼執行結果如下:
在兩條分割線之間的語句u.get_deleter()(p)
,就等同於調用deleter函數/仿函數。
6.bool操作重載
沒有需要特殊說明的。
7.release()
該操作會釋放指針的所有權,並通過返回值返回。此調用不會銷毀托管對象,但unique_ptr對象將從刪除對象的責任中釋放。某些其他實體必須負責刪除對象。
請看如下代碼:
int main(int agrc, char* argv[]) {
unique_ptr<smart_point_class, deleter> u(new smart_point_class(1), deleter());
u.release();
}
上述代碼執行結果如下:
會發現並沒有對u存儲的指針進行銷毀的操作,這樣非常容易造成內存泄漏。代碼應該改為如下形式:
int main(int agrc, char* argv[]) {
unique_ptr<smart_point_class, deleter> u(new smart_point_class(1), deleter());
smart_point_class *p = u.release();
delete p;
}
上述代碼執行結果如下:
8.reset()
銷毀當前由unique_ptr管理的對象(如果有的話),並獲取p的所有權。如果p是一個空指針(例如默認初始化的指針),unique_ptr將變為空,在調用之后不管理任何對象。若要釋放存儲指針的所有權而不破壞它,請使用成員函數release。
release使用例子:
int main(int agrc, char* argv[]) {
unique_ptr<smart_point_class> u1(new smart_point_class(1));
cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
smart_point_class *p (new smart_point_class(2));
cout << "----------------------------------------------------" << endl;
u1.reset(p);
cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
cout << "----------------------------------------------------" << endl;
u1.reset();
cout << "----------------------------------------------------" << endl;
}
上述代碼執行結果如下:
- 在執行完
u1.reset(p)
語句之后,會首先銷毀資源,然后u1所管理內存的value由1變為2,對應的地址也有了改變; - 在執行完
u1.reset()
語句之后,會銷毀u1現在所管理內存的資源。
9.swap()
交換兩個unique_ptr所管理的對象。
swap使用例子:
int main(int agrc, char* argv[]) {
unique_ptr<smart_point_class> u1(new smart_point_class(1));
unique_ptr<smart_point_class> u2(new smart_point_class(2));
cout << "----------------------------------------------------" << endl;
cout << "Before swap :" << endl;
cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
cout << "u2 value : " << u2->get_value() << '\n' << " addredd : " << u2.get() << endl;
cout << "----------------------------------------------------" << endl;
u1.swap(u2);
cout << "After swap :" << endl;
cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
cout << "u2 value : " << u2->get_value() << '\n' << " addredd : " << u2.get() << endl;
cout << "----------------------------------------------------" << endl;
}
上述代碼執行結果如下:
可以看到u1和u2所管理的對象做了交換操作。
10. * ->操作
與普通指針操作相同,在此不再贅述。