0%

From auto_ptr to unique_ptr

C語言時期

在C語言中,最常遇到的情況就是忘了釋放記憶體,然後造成Memory Leak的問題。
例如以下這段程式

1
2
3
4
5
6
7
8
9
10
11
void LeakDemo(char path1, char path2)
{
void *alloc = malloc(16);
if (path1) {
free(alloc);
return;
}
if (path2) return;
free(alloc);
return;
}

在上面這段程式裡面,path2就忘了釋放記憶體,然後造成Leak。
這是語言上的侷限,只能靠多檢查source code跟使用工具來減少這種問題。

C++98時期

由於C++有RAII idiom之後,對於釋放記憶體的事情就變得簡單很多了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct StackObject {
void *alloc;
StackObject(int size) {
alloc = malloc(size);
}
~StackObject() {
free(alloc);
}
};
void NoLeakDemo(char path1, char path2)
{
StackObject s(16);
if (path1) return;
if (path2) return;
return;
}

而為了一般化,STL裡面時做了一個auto_ptr,利用Template跟RAII的觀念來管理記憶體。

auto_ptr的問題

如果把auto_ptr侷限於上面的用法,不會遇上什麼問題,一旦要搭配現有的程式碼,當參數傳來傳去,問題就出現了其中最嚴重的問題莫過於

1
2
3
4
5
6
7
void Test(auto_ptr<int> v) {}
int main()
{
auto_ptr<int> v(new int(4));
Test(v);
printf("%d\n", *v);
}

以上的程式碼,看起來沒什麼問題,不過實際執行就Crash了。
auto_ptr的問題是沒有Copy Semantics,他的Copy Constructur裡面做的是Move的動作。
以下是auto_ptr的Conpy Constructor實作簡化版

1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename _Tp>
class auto_ptr {
private:
_Tp* _M_ptr;
public:
auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) {}
_Tp* release() throw()
{
element_type* __tmp = _M_ptr;
_M_ptr = 0;
return __tmp;
}
};

於是在執行完Test之後,main中的v裡面的pointer就被清空,無法正常使用。
由於這個緣故,STL的各種容器跟演算法,搭配上auto_ptr或多或少都有問題。在經過多次修改之後還是無法修復,於是auto_ptr在C++0x之後就標繼承deprecated了,不建議使用。
由於C++0x引進了Rvalue reference,因此新的unique_ptr就此登場。

unique_ptr

unique_ptr能做的事幾乎跟auto_ptr一樣,除了少數例外。

1
2
3
4
auto_ptr<int> a(new int);
auto_ptr<int> b(a); // Compile success, but nosafe
unique_ptr<int> c(new int);
unique_ptr<int> d(c); // Compile error

不過你也可以利用C++0x新增的Move Semantics,手動進行Move動作。

1
2
unique_ptr<int> c(new int);
unique_ptr<int> d(std::move(c)); // OK

靠著C++0x新增的Rvalue Reference,區分出Copy跟Move的差異。看著unique_ptr的實作,他只允許Move Constructor,而不允許Copy Constructor。

1
2
3
4
5
6
7
8
9
10
template<class _Ty, class _Dx>	
class unique_ptr
{
private:
unique_ptr(const _Myt&); // not defined
unique_ptr& operator=(const _Myt&); // not defined
public:
unique_ptr(unique_ptr&& _Right) _NOEXCEPT;
unique_ptr& operator=(unique_ptr&& _Right) _NOEXCEPT;
};

如果要得到更多的Rvalue Refenece的相關內容,請參考這篇Rvalue References: C++0x Features in VC10, Part 2

unique_ptr還能做些什麼

由於C++0x引進了Move Semantics,連帶的STL所有Container跟Algorithmer都支援Move Semantics了,因此這樣的程式碼就變得可行了。

1
2
3
4
5
6
7
8
typedef unique_ptr<char> UniCharPtr;
vector<UniCharPtr> vc;
vc.push_back(UniCharPtr(new char('c')));
vc.push_back(UniCharPtr(new char('b')));
vc.push_back(UniCharPtr(new char('a')));
sort(vc.begin(), vc.end(), [] (const UniCharPtr &a, const UniCharPtr &b) -> bool {
return *a < *b;
});

在這裡同樣用上了lambda expression,留待有空再寫。

有個手法稱作source and sink idiom
因為有了Move Semantics更容易的實現。

1
2
3
4
unique_ptr<int> Source() { return unique_ptr<int>(new int); }
void Sink(unique_ptr<int> s) {}
auto source = Source();
Sink(std::move(source));

除此之外,還能夠自訂Destructor。

1
2
3
4
5
auto del = [](int *p) { 
cout << "Deleting x, value is : " << *p << endl;
delete p;
};
std::unique_ptr<int, decltype(del)> px(new int(5), del);

以及透過partial specialization來管理一個動態產生的Array。

1
2
auto del = [](int p[]) { delete [] p; };
std::unique_ptr<int [], decltype(del)> px(new int[5], del);