配列への対応とmake_unique
auto_ptrにはできなかったことをもう一つ。
auto_ptrでは配列を確保することができません...いや、確保された配列を正しく解放できません。auto_ptrのデストラクタは捕まえているポインタに対しdelete p;するんですけど、配列に対してはdelete[] p;ですからね。
unique_ptrならテンプレート引数に(foo[]のように)配列を与えておけば、デストラクト時にちゃんとdelete[]してくれます。
int main() {
const int N = 3;
unique_ptr<foo[]> p(new foo[N]);
for ( int i = 0; i < N; ++i ) { cout << p[i].symbol() << ' '; }
cout << endl;
}
/* 実行結果
foo(0) [BORN]
foo(0) [BORN]
foo(0) [BORN]
foo(0) foo(0) foo(0)
foo(0) [DEAD]
foo(0) [DEAD]
foo(0) [DEAD]
*/
さらに、Visual C++ 2013にはC++14でサポート予定のmake_uniqueが一足お先に追加されています。これ使えばdeleteだけでなくnewともオサラバできます。
int main() {
unique_ptr<foo> p;
p = make_unique<foo>(); // new foo()
p = make_unique<foo>("adam"); // new foo("adam")
p = make_unique<foo>("eve", 19); // new foo("eve",19)
const int N = 3;
auto q = make_unique<foo[]>(N); // new foo[N]
for ( int i = 0; i < N; ++i ) { cout << q[i].symbol() << ' '; }
cout << endl;
}
/* 実行結果
foo(0) [BORN]
adam(0) [BORN]
foo(0) [DEAD]
eve(19) [BORN]
adam(0) [DEAD]
foo(0) [BORN]
foo(0) [BORN]
foo(0) [BORN]
foo(0) foo(0) foo(0)
foo(0) [DEAD]
foo(0) [DEAD]
foo(0) [DEAD]
eve(19) [DEAD]
*/
カスタム・デリータ
unique_ptrの定義はこんなカンジになっています:
namespace std {
template<typename T, typename D =default_delete<T>>
class unique_ptr { ... };
}
テンプレート引数は実は2つあって、2つめのDがポインタの解放を行うデリータとして機能します。デフォルトで用いられるstd::default_deleteは:
namespace std {
template<typename T>
struct default_delete {
void operator()(T* p) const { delete p; }
};
}
のような定義です。unique_ptrはポインタを手放す際にデリータのoperator()を呼ぶことで後始末を行っています。
なので、このデリータ(後始末オブジェクト)を差し替えれば、delete以外の後始末をやらせることができます。
delete p;のかわりにfclose(p);してくれるデリータを用意すれば、使い終わったら勝手にクローズされるFILE*が実現できますね、クラス・テンプレートの特殊化を使ってやってみましょう。
// specialization
template<> struct std::default_delete<FILE> {
void operator()(FILE* p) const {
fclose(p);
cout << "the FILE is closed." << endl;
}
};
int main() {
auto p = make_unique<foo>("adam",20);
auto file = unique_ptr<FILE>(fopen("foo.txt","w"));
fprintf(file.get(), "I am %s\n", p->symbol().c_str());
}
/* 実行結果
adam(20) [BORN]
the FILE is closed.
adam(20) [DEAD]
*/
あるいは例えばdelete p;のかわりにCloseHandle(p);すれば、使い終わったら勝手にクローズしてくれるWindows HANDLEが実現できます。この場合、HANDLEの正体はvoid*なのでクラス・テンプレートの特殊化だとあらゆるvoid*に対してCloseHandleしてしまって迷惑千万。HANDLE専用のカスタム・デリータを作り、unique_ptrのコンストラクタに与えることにします。
#include <Windows.h>
#include <iostream>
#include <memory>
#include <functional>
using namespace std;
// custom deleter
typedef std::unique_ptr<void,std::function<void(HANDLE)>> unique_win_handle;
unique_win_handle make_win_thread(
LPTHREAD_START_ROUTINE routine,
LPVOID arg,
LPDWORD pID = nullptr) {
// ラムダ式で定義したカスタム・デリータを与える
return unique_win_handle(
CreateThread(nullptr, 0, routine, arg, 0, pID),
[](HANDLE h) { CloseHandle(h); cout << "the HANDLE closed\n"; });
}
DWORD __stdcall thr_fun(LPVOID arg) {
int n = *static_cast<int*>(arg);
for ( int i = 0; i < n; ++i ) {
cout << "something wonderful!!\n";
}
return 0;
}
int main() {
int n = 3;
auto thr = make_win_thread(&thr_fun, &n);
WaitForSingleObject(thr.get(), INFINITE);
}
/* 実行結果
something wonderful!!
something wonderful!!
something wonderful!!
the HANDLE closed
*/
...いかがなもんでしょ、これだけ手間要らずになったC++、毛嫌いされる理由がまた1つ減ってくれたかしら。
