unique_ptrにポインタ以外のもの
を持たせるとき
okada(@okdshin)
unique_ptrって?
C++11で登場したスマートポインタ
auto_ptrの完全上位互換
スコープを抜けたら自動でdelete
#include <iostream>
#include <memory> // for unique_ptr
class Widget {
public:
~Widget() { std::cout << "deleted" << std::endl; }
};
int main() {
std::unique_ptr<Widget> wp{new Widget{}};
}
//output:
// deleted
newだ!! 殺せ!!!!
int main() {
//std::unique_ptr<Widget> wp{new Widget{}};
auto wp = std::make_unique<Widget>();
}
※make_uniqueはC++14から登場
unique_ptrは便利
リソースを自動で開放してくれる
効率もデフォルトだとポインタと同程度で悪くない
STLコンテナの要素にできる(もちろんvectorも!)
デリータも設定できる
unique_ptrにデリータを設定
// Widgetのファクトリ関数
decltype(auto) make_widget() {
auto deleter = [](Widget* wp) {
std::cout << "deleter is called" << std::endl;
delete wp;// きちんとdeleteしておく
};
return std::unique_ptr<Widget, decltype(deleter)>{
new Widget{}, std::move(deleter)};
}
int main() {
auto wp = make_widget();
}
//output:
// deleter is called
// deleted
※デリータを設定する場合はmake_uniqueは使えない
unique_ptrからshared_ptrに変換
int main() {
// デリータごとunique_ptrをshared_ptrに変換可能
{
std::shared_ptr<Widget> shared_wp{make_widget()};
}
{
auto wp = make_widget();
std::shared_ptr<Widget> shared_wp{std::move(wp)};
}
}
デリータもきちんと引き継がれる
unique_ptr → shared_ptrは簡単にできるが逆はできない
∴ファクトリ関数の返り値型はshared_ptrではなく
unique_ptrにするのがgood
じゃあ、リソースハンドルが
ポインタじゃなくてもいいんじゃ
ないの?
リソースハンドルがポインタじゃなかったら?
namespace others /*他人のAPI*/ {
int GetHandle() {/*略*/} // リソースハンドルはint型
void ReleaseHandle(int handle) {/*略*/}
}
decltype(auto) make_unique_handle() {
auto deleter = [](int handle) {
others::ReleaseHandle(handle); // deleteの代わりに開放関数を呼ぶ
};
// コンパイルエラー!! GetHandle()の返り値はポインタじゃないよ!!!
return std::unique_ptr<int, decltype(deleter)>(
others::GetHandle(), std::move(deleter));
}
int main() { auto h = make_unique_handle(); }
単に置き換えただけではコンパイルできない
デリータの中でpointer型を指定
namespace others {/*略*/}
// 昔ながらの関数オブジェクトクラス
struct deleter {
using pointer = int; // デフォルトではint*になるのを、intと指定
void operator()(int handle) { others::ReleaseHandle(handle); }
};
decltype(auto) make_unique_handle() {
return std::unique_ptr<int, deleter>(
others::GetHandle(), deleter{});
}
int main() { auto h = make_unique_handle(); }
※ラムダではメンバ型が宣言できないため
関数オブジェクトクラスを使う
やったか……?
やってない
error: invalid operands of types
'int' and 'std::nullptr_t' to binary 'operator!='
if (__ptr != nullptr)
^
intとnullptrの比較ができずコンパイルエラーに
どうしようもない
bool operator!=(int, std::nullptr_t)は宣言できない
bool my::operator!=(int, std::nullptr_t)' must have an argument of
class or enumerated type
bool operator!=(int i, std::nullptr_t n);
^
もしハンドルが組み込み型じゃなくて、ユーザ定義型
だったらできるけど、そもそもなんでハンドルをnullptr
なんかと比較しなくちゃいけないの?
しょうがないので自分で型を書く
class unique_handle {
void safe_release() {
if(handle_ == others::NullHandle) { return; }
others::ReleaseHandle(handle_);
handle_ = others::NullHandle;
}
int handle_;
public:
unique_handle() : handle_(others::GetHandle()) {}
unique_handle(unique_handle&& rhs)
: handle_{others::NullHandle} {
std::swap(handle_, rhs.handle_);
}
decltype(auto) operator=(unique_handle&& rhs) {
safe_release(); std::swap(handle_, rhs.handle_);
return *this;
}
め、めんどくせえーッ!!!
例外安全性は?
ムーブはちゃんと実装できてる?
リソース漏れ本当にしない?
どのハンドルに対しても実装はほとんど同じなのにハ
ンドルごとに毎回書くの?
任意の型のリソースハンドルに
スコープを抜けたら自動で指定し
たデリータを実行してくれて
ムーブできて
標準ライブラリ並に信頼できる
RAIIラッパがほしい!!!!
あります。
N4189 Generic Scope Guard RAII
Wrapper for the Standard Library
unique_resource
unique_reource
C++標準化委員会のペーパーで次期標準ライブラリに加
えることを提案されているライブラリ
unique_ptrの一般化
unique_resourceによる実装
namespace others {/*略*/}
decltype(auto) make_unique_handle() {
return std::experimental::make_unique_resource(
others::GetHandle(), &others::ReleaseHandle);
}
int main() {
auto h1 = make_unique_handle();
auto h2 = make_unique_handle();
auto h3 = make_unique_handle();
h2 = std::move(h3); // move可能 h2の元々のハンドルはリリース
auto h4 = make_unique_handle();
std::cout << h4.get() << std::endl; // 生のハンドルにアクセス
h4.reset(); // 明示的にハンドルをリリース
}
至極簡単に実装できる
「で? unique_resourceは
いつ使えるようになるの?」
「N4189にExample実装があるから
コピペしたら今すぐ使えるよ」
N4189のunique_resourceは
C++14対応コンパイラで使える
C++11でも少し修正すれば使える
まとめ
unique_ptrはデリータをカスタマイズできるが、ポイ
ンタ型のハンドルしか持てない
非ポインタ型ハンドルの自動リソース管理のために
unique_resourceが提案されている
N4189にunique_resourceの実装例があり、コピペした
ら使える
おまけ
std::threadのRAIIラッパ
Effective Modern C++ Item37
std::threadはjoinableの状態でデストラクタが呼ばれると
プログラムを終了させてしまう
int main() {
// tが処理を終える前にデストラクタが呼ばれると実行時エラーが起こる
std::thread t{[](){ std::cout << "hello" << std::endl; }};
}
そのためstd::threadはどのような実行経路でもデストラ
クタが呼ばれる前に非joinable状態にする必要がある
すなわちデストラクタを呼ぶ前にjoinかdetachする
まさにリソース管理の問題
EMC++ Item37における解決法
自分でRAIIクラスを書く
class unique_thread { // EMC++ではThreadRAII
public:
// デストラクト時の動作を指定できる
enum class dtor_action { join, detach };
unique_thread(std::thread&& t, dtor_action a)
: action_{a}, thread_{std::move(t)} {}
~unique_thread() {
if(thread_.joinable()) {
if(action_ == dtor_action::join) {
thread_.join();
}
else {
thread_.detach();
}
}
unique_resourceを使った実装
デリータとしてラムダを渡すだけでOK
enum class unique_thread_dtor_action { join, detach };
template<typename Func>
decltype(auto)
make_unique_thread(Func&& func, unique_thread_dtor_action action) {
return std::experimental::make_unique_resource(
std::thread{std::forward<Func>(func)},
[action](auto& thread) {
if(thread.joinable()) {
if(action == unique_thread_dtor_action::join) {
thread.join();
}
else {
thread.detach();
}
}
});
参考
N4189 Generic Scope Guard and RAII Wrapper for the
Standard Library (Peter Sommerlad & Andrew L.Sandoval
2014)
Effective Modern C++ (Scott Meyers 2015)

unique_ptrにポインタ以外のものを持たせるとき