智能指針(smart pointer)是一種抽象的數據類型(abstract data type)。 在程序設計中, 智能指針通常由類模板(class template)實現,。 通常藉助模板達到泛型,藉助類的析構函數來達成自動釋放指針所指向的內存或者對象。
智能指針模擬了指針的所有的屬性, 然後在加上指針沒有的額外的features, 這些features包括自動內存管理(automatic memory mangement), 或者bouds checking。 之所以加上這些特性, 是因爲這些額外的特性具有很多好的作用, 能夠減少因爲使用指針不當而造成的bugs, 並且保持高效型。
Misuse of pointers通常是造成bugs的主要的源頭所在。 使用智能指針的自動內存回收機制(automatic memory deallocation)能夠避免出現內存泄露(memory leaks)。 更一般的, 他們能夠進行對象的自動析構:當對象的最後一個owner(即智能指針)被destroyed的時候, 智能指針的控制着(或者擁有的)的對象就可以自動被destroyed。 舉個例子, 如果對象的owner(智能指針)是一個local variable的時候, 當execution運行到智能指針的scope之外的時候, 那麼智能指針指向的對象也能夠自動destroyed了。 智能指針通過推遲對象的destruction, 直至object不再被使用, 才進行對象的析構, 這樣就不會再有dangling pointers了。 這也就不需要我們手動的去delete了。
智能指針有很多種:
(1) 一些智能指針有reference counting(引用計數)的機制(shared_ptr)。 這很節省內存。 Linux裏面管理文件Inode就是採用的引用計數的機制。
(2)一些智能指針通過對ownership of an object進行賦值(unique_ptr)。 這個對象只能一次被一個指針指向。
C++的智能指針是通過模板了類實現的。 利用operator overloading的方式, 這樣智能指針就具有了除了原始指針(raw pointer)的一些behavior(例如dereference, assignment)之外, 還提供額外的內存管理特徵(memory management features)。
C++11中提供了std::unique_ptr, 定義在<memory>頭文件中。 std::unique_ptr替代了原先的std::auto_ptr, 也就是auto_ptr是deprecated, 基本上現在最後不用auto_ptr。
C++11新增了move語義。 相比於copy語義, move能更好的實現值傳遞。 std::auto_ptr使用的是copy語義。 爲了先前兼容, C++11沒有修改std::auto_ptr, 而是引入了新的使用move語義的std::unique_ptr。
auto_ptr(現在已經不再用了)不能作爲容器(例如vector)的元素, 而unique_ptr可以作爲容器的元素。
unique_ptr的拷貝構造函數和賦值運算符都聲明爲deleted。 也就是說它不能被拷貝, 只能通過std::move來傳遞它指向的內存的所有權。 下面的例子說的很清楚:
<span style="font-size:14px;">std::unique_ptr<int> p1(new int(5)); // new int(5)在heap中分配一個能存一個int的內存, p1指向這個int的對象
std::unique_ptr<int> p2 = p1; // 編譯會出錯, 即對unique_ptr不能拷貝構造和賦值, 因爲賦值運算符被聲明ideleted
std::unique_ptr<int> p3 = std::move(p1); // move了, 轉移所有權, 現在那塊內存歸p3所有, p1成爲無效的指針.
p3.reset(); //釋放內存.
p1.reset(); //實際上什麼都沒做.</span>
使用unique_ptr的一個例子:
<span style="font-size:14px;">#include <iostream>
#include <memory>
struct Foo {
Foo() { std::cout << "Foo::Foo\n"; }
~Foo() { std::cout << "Foo::~Foo\n"; }
void bar() { std::cout << "Foo::bar\n"; }
};
void f(const Foo &foo)
{
std::cout << "f(const Foo&)\n";
}
int main()
{
std::unique_ptr<Foo> p1(new Foo); // p1 owns Foo
if (p1) p1->bar();
{
std::unique_ptr<Foo> p2(std::move(p1)); // now p2 owns Foo, p1對對象沒有ownership
f(*p2);
p1 = std::move(p2); // ownership returns to p1
std::cout << "destroying p2...\n";
}
if (p1) p1->bar();
// Foo instance is destroyed when p1 goes out of scope
}</span>
運行結果如下:
shared_ptr和weak_ptr
定義在頭文件<memory>中。
基於Boost庫, C++引入了另外兩個智能shared_ptr和weak_ptr。 他們最早在TR1中引入, 但是在C++11中, 在Boost的基礎上又加入了新的功能。
std::shared_ptr使用的是引用計數(reference counting)。 每一個shared_ptr的拷貝都指向相同的內存。 在最後一個shared_ptr析構的時候, 內存纔會被釋放(當引用計數變成0了)。
<span style="font-size:14px;">std::shared_ptr<int> p1(new int(5));
std::shared_ptr<int> p2 = p1; // 都指向同一內存.
p1.reset(); // 因爲p2還在,所以內存沒有釋放. 也就是說int(5)還存在着
p2.reset(); // 釋放內存, 因爲沒有shared_ptr指向那塊內存了(即reset後, 引用計數變成0了). 即int(5)那塊內存被回收了</span>
關於shared_ptr的reset函數:
原型:
void reset();
|
(1) | (since C++11) |
template< class Y > void reset( Y* ptr ); |
(2) | (since C++11) |
template< class Y, class Deleter > void reset( Y* ptr, Deleter d ); |
(3) | (since C++11) |
template< class Y, class Deleter, class Alloc > void reset( Y* ptr, Deleter d, Alloc alloc ); |
(4) | (since C++11) |
For signature (1) the object becomes empty (as if default-constructed).
In all other cases, the shared_ptr acquires ownership of p with a use
count of 1, and -optionally- with del and/oralloc as deleter and allocator, respectively.
Additionally, a call to this function has the same side effects as if shared_ptr's destructor was called before its value changed
(including the deletion of the managed object if this shared_ptr was unique).
std::shared_ptr使用引用計數, 所以就有循環計數的問題(也就是說,一個對象裏面有一個shared_ptr管理着一個對象, 而後面這個被管理的對象又有一個shared_ptr管理着前面這個對象。 二者共存了, 都不能死,這就造成資源泄露)。 爲了打破循環, 可以使用std::weak_ptr, 顧名思義, weak_ptr是一個弱引用: 只引用。 不計數。 如果內存被shared_ptr和weak_ptr同時引用, 當所有的shared_ptr析構之後, 不管還有沒有weak_ptr引用該內存的時候, 內存也會被釋放。 所以weak_ptr不保證它指向的內存一定有效, 所以用之前需要檢查:
關於weak_ptr的lock()函數:
原型:
std::shared_ptr<T> lock() const
|
(since C++11) |
reates a new shared_ptr
that
shares ownership of the managed object. If there is no managed object, i.e. *this is
empty, then the returned shared_ptr
also
is empty.
返回值
A shared_ptr
which shares ownership of the owned object.
注意點:
Creates a new shared_ptr
that
shares ownership of the managed object. If there is no managed object, i.e. *this is
empty, then the returned shared_ptr
also
is empty.
<span style="font-size:14px;"></span><pre name="code" class="cpp">#include <iostream>
#include <memory>
using namespace std;
int main() {
std::shared_ptr<int> p1(new int(5));
std::weak_ptr<int> wp1 = p1; // 還是隻有p1有所有權. 弱指針只引用,不計數
{
std::shared_ptr<int> p2 = wp1.lock(); // p1和p2都有所有權
if(p2) // 使用前需要檢查
{
cout << "看到了共享指針p1(也是弱指針wp1所指的)所指的對像(new int(5))" << endl;
}
} // p2析構了, 現在只有p1有所有權.
p1.reset(); // 對象的引用計數變成0了。 內存被釋放.
std::shared_ptr<int> p3 = wp1.lock(); // 因爲內存已經被釋放了, 所以得到的是空指針.
if(p3)
{ //不會運行到這裏
cout << "看到了嗎" << endl;
}
return 0;
}
運行結果如下:
如果將上述的weak_ptr改爲shared_ptr:
#include <iostream>
#include <memory>
using namespace std;
int main() {
std::shared_ptr<int> p1(new int(5));
std::shared_ptr<int> p2 = p1; // 還是p1, p2均有所有權, 對象的應用技術變爲2
{
std::shared_ptr<int> p3 = p2; // p1,p2,p3都有所有權, 引用計數變成3
cout << *p3 << endl;
} // p3析構了, 現在只有p1, p2有所有權.
p1.reset(); // p1析構了, 只有p2有所有權, 引用計數變成1.
cout << *p2 << endl;
p2.reset(); // 應用計數變成0, 對象被釋放
if(p2) { // p2變成NULL了
cout << "看到嗎" << endl;
}
return 0;
}
運行如下:
在舉一個例子:
#include <iostream>
#include <memory>
#include <thread>
void observe(std::weak_ptr<int> weak)
{
std::shared_ptr<int> observe(weak.lock());
if (observe) {
std::cout << "\tobserve() able to lock weak_ptr<>, value=" << *observe << "\n";
} else {
std::cout << "\tobserve() unable to lock weak_ptr<>\n";
}
}
int main()
{
std::weak_ptr<int> weak;
std::cout << "weak_ptr<> not yet initialized\n";
observe(weak);
{
std::shared_ptr<int> shared(new int(42));
weak = shared;
std::cout << "weak_ptr<> initialized with shared_ptr.\n";
observe(weak);
}
std::cout << "shared_ptr<> has been destructed due to scope exit.\n";
observe(weak);
}
運行結果如下:
關於智能指針使用的幾大注意:
問題一 shared_ptr和unique_ptr, 何時使用share_ptr智能指針, 何時使用unique_ptr
無疑, 默認情況下, 要傾向於使用unique_ptr。 而且你可以在後面隨時使用(move)轉化成shared_ptr。
如果你一開始就知道需要shared ownership(需要對象所有權共享), 你就可以直接通過make_shared獲得shared_ptr。
關於當“when in doubt, prefer unique_ptr”, 原因有如下三種:
首先, 使用最簡單的語義已經足夠了: 選擇合適的只能指針去直接表達你的意圖, 以及你希望你的指針指針做什麼. 如果你創建出了一個新的對象, 但是你不知道你的這個對象最終需要被用於共享所有權,此時可以使用只能指針 unique_ptr 去表達這個對象被用於唯一的所有權(unique ownership). 這樣你仍然可以把這個對象保存在一個容器中(container)(例如,vector<unique_ptr<widget>>) , 並且你可以對待普通指針(raw pointer)一樣安全的進行相關操作。如果你在後面需要共享unique_ptr指向的對象, 我們總是可以把unique_ptr 轉型成爲 shared_ptr, 這樣我們就實現了對這個對象(unique指向的對象)共享了。
其次, unique_ptr 比shared_ptr智能指針更加高效. 一個 unique_ptr 並不需要 維護 reference count 信息, 無論如何把, 就和raw pointer那麼高效. 一句話, 只有在必要的時候纔會增加overheads(把unique_ptr變成shared_ptr)。
第三,使用 unique_ptr 使得你後面更加的flexible. 如果你最開始就使用 unique_ptr, 後面你總可以使用move將unique_ptr轉換成 shared_ptr ,甚至直接通過get() 或者release()轉換成another custom smart pointer (甚至是 raw pointer) .
問題二: 爲什麼幾乎總是使用 make_shared 去創建一個 被shared_ptr擁有的對象?
很簡單, 使用make_shared (或者, 如果你需要一個custome的allocator, 使用 allocate_shared) ? 主要是基於兩個原因: 簡單, 而 高效.
首先, 使用make_shared, 使得我們的代碼更加簡潔.
其次, 使用make_shared 更加高效. 首先shared_ptr 的實現需要記錄下被所有的shared_ptrs 和 weak_ptrs 指代的共享的的對象. 特別的housekeeping 信息需要包含兩個 reference counts(引用計數):
- 強引用 “strong reference” count記錄the number of shared_ptrs 使得當前的object alive的shared_ptr的數目.當最後一個強引用被delete的時候, 共享的object 就會被被 destroyed (and possibly deallocated) 。
- 弱引用 “weak reference” count 記錄the number of weak_ptrs currently observing the object. 當最後一個弱用用被刪除的時候, 共享的 housekeeping control block 被destroyed and deallocated (and the shared object is deallocated if it was not already) 。
使用make_shared的好處可以通過如下兩個圖可以看見:
沒有使用make_shared的時候, 如下:
使用shared_ptr的時候:
不難看出, 使用make_shared的好處如下:
當你要做的事情可以通過系統進行一次函數調用就完成, 你就應該給系統這個機會去把這個工作更加高效的完成 . 這就如你可以使用range-insert的函數調用一次把 100個元素插入到一個 vector中去。v.insert(
first, last ) 而不是100 次調用 v.insert(
value )。 這就是爲什麼我們要只需一次的調用 make_shared 而不是分別先後單獨調用 new
widget() 和 shared_ptr(
widget* ).
使用 make_shared 函數去避免
explicit new 從而避免異常安全問題的發生.
同理 make_unique 也是類似的問題。
sink( unique_ptr<widget>{new widget{}},unique_ptr<gadget>{new gadget{}} ); // Q1: do you // see the problem?
、
sink( make_unique<widget>(),unique_ptr<gadget>{new gadget{}} ); // Q2: is this better?(1)make shared 的使用辦法:
原型:
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
所在頭文件: <memory>
作用:
Constructs an object of type T
and
wraps it in a std::shared_ptr using args
as
the parameter list for the constructor of T
.
返回值:
std::shared_ptr of
an instance of type T
.
使用例子:
#include <iostream>
#include <memory>
void foo(const std::shared_ptr<int>& i)
{
(*i)++;
}
int main()
{
auto sp = std::make_shared<int>(12);
foo(sp);
std::cout << *sp << std::endl;
}
運行結果:
(2)make_unique的用法:位於頭文件<memory>中
原型:
template< class T, class... Args > unique_ptr<T> make_unique( Args&&... args ); |
(1) |
(since C++14) (only for non-array types) |
template< class T > unique_ptr<T> make_unique( std::size_t size ); |
(2) |
(since C++14) (only for array types with unknown bound) |
template< class T, class... Args > /* unspecified */ make_unique( Args&&... args ) = delete; |
(3) |
(since C++14) (only for array types with known bound) |
返回值:
std::unique_ptr of
an instance of type T
.
作用:
Constructs an object of type T
and
wraps it in a std::unique_ptr.
使用例子:
<span style="font-size:14px;">#include <iostream>
#include <memory>
struct Vec3
{
int x, y, z;
Vec3() : x(0), y(0), z(0) { }
Vec3(int x, int y, int z) :x(x), y(y), z(z) { }
//重載運算符
friend std::ostream& operator<<(std::ostream& os, Vec3& v) {
return os << '{' << "x:" << v.x << " y:" << v.y << " z:" << v.z << '}';
}
};
int main()
{
// Use the default constructor.
std::unique_ptr<Vec3> v1 = std::make_unique<Vec3>();
// Use the constructor that matches these arguments
std::unique_ptr<Vec3> v2 = std::make_unique<Vec3>(0, 1, 2);
// Create a unique_ptr to an array of 5 elements
std::unique_ptr<Vec3[]> v3 = std::make_unique<Vec3[]>(5);
std::cout << "make_unique<Vec3>(): " << *v1 << '\n'
<< "make_unique<Vec3>(0,1,2): " << *v2 << '\n'
<< "make_unique<Vec3[]>(5): " << '\n';
for (int i = 0; i < 5; i++) {
std::cout << " " << v3[i] << '\n';
}
}
</span>
由於電腦的g++版本太低, 不支持C++14, 而make_unique是C++14的內容, 所以運行不了。
需要換一個支持C++14的編譯器:就使用了在線編譯器:
網址:
http://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique
(3)關於使用std::move, 位於頭文件<utility>中
原型:
template< class T > typename std::remove_reference<T>::type&& move( T&& t ); |
|
(since C++11) (until C++14) |
template< class T > constexpr typename std::remove_reference<T>::type&& move( T&& t ); |
|
(since C++1 |
返回值:
static_cast<typename std::remove_reference<T>::type&&>(t)
作用:
例子:
<span style="font-size:14px;">#include <iostream>
#include <utility>
#include <vector>
#include <string>
int main()
{
std::string str = "Hello";
std::vector<std::string> v;
// uses the push_back(const T&) overload, which means
// we'll incur the cost of copying str
v.push_back(str);
std::cout << "After copy, str is \"" << str << "\"\n";
// uses the rvalue reference push_back(T&&) overload,
// which means no strings will be copied; instead, the contents
// of str will be moved into the vector. This is less
// expensive, but also means str might now be empty.
v.push_back(std::move(str));
std::cout << "After move, str is \"" << str << "\"\n";
std::cout << "The contents of the vector are \"" << v[0]
<< "\", \"" << v[1] << "\"\n";
// string move assignment operator is often implemented as swap,
// in this case, the moved-from object is NOT empty
std::string str2 = "Good-bye";
std::cout << "Before move from str2, str2 = '" << str2 << "'\n";
v[0] = std::move(str2);
std::cout << "After move from str2, str2 = '" << str2 << "'\n";
}
</span>
運行結果:
可見, 對於string賦值, Move就是執行的swap啊。