More Related Content Similar to unique_ptrにポインタ以外のものを持たせるとき (19) unique_ptrにポインタ以外のものを持たせるとき6. 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は使えない
7. 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
9. リソースハンドルがポインタじゃなかったら?
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(); }
単に置き換えただけではコンパイルできない
10. デリータの中で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(); }
※ラムダではメンバ型が宣言できないため
関数オブジェクトクラスを使う
14. しょうがないので自分で型を書く
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;
}
20. 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(); // 明示的にハンドルをリリース
}
至極簡単に実装できる
25. Effective Modern C++ Item37
std::threadはjoinableの状態でデストラクタが呼ばれると
プログラムを終了させてしまう
int main() {
// tが処理を終える前にデストラクタが呼ばれると実行時エラーが起こる
std::thread t{[](){ std::cout << "hello" << std::endl; }};
}
そのためstd::threadはどのような実行経路でもデストラ
クタが呼ばれる前に非joinable状態にする必要がある
すなわちデストラクタを呼ぶ前にjoinかdetachする
まさにリソース管理の問題
26. 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();
}
}
27. 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();
}
}
});
28. 参考
N4189 Generic Scope Guard and RAII Wrapper for the
Standard Library (Peter Sommerlad & Andrew L.Sandoval
2014)
Effective Modern C++ (Scott Meyers 2015)