Effective Modern C++勉強会
#22
2015/05/20
福田 圭祐
@keisukefukuda
http://bit.ly/1KjcGeb
Item 22
pImplイディオムを使うときは
特殊メンバ関数を実装ファイルに記述しよう
http://bit.ly/1KjcGeb
pImplイディオムとは (1)
pImplイディオムを使わない場合の問題点
#include “gadget.h”
class Widget {
public:
Widget();
…
private:
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
Widgetを使うクライアントコードは、
gadget.h, widget.h, vector, string
に依存
• includeするファイルが多いことによる
コンパイル時間増大
• gadget.h と widget.h が変更されるたびに
クライアントコードも再コンパイルされる
[widget.h]
http://bit.ly/1KjcGeb
pImplイディオム =
– C++98からよく知られたテクニック
– インターフェースと実装を分離することによりヘッダファイ
ルの依存関係を軽減
pImplイディオムとは (2)
class Widget {
public:
Widget();
~Widget();
…
private:
struct Impl;
struct Impl *pImpl;
};
[widget.h]
#include “widget.h”
#include “gadget.h”
#include <vector>
#include <string>
struct Widget::Impl {
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
Widget::Widget() : pImpl(new Impl) { }
Widget::~Widget() { delete pImpl; }
[widget.cpp]
http://bit.ly/1KjcGeb
C++11:スマートポインタの使用
C++98におけるpImplイディオム
= 生ポインタの使用
_人人人人人人_
> 前世紀的 <
 ̄Y^Y^Y^Y^Y ̄
→ スマートポインタを使おう
http://bit.ly/1KjcGeb
std::unique_ptr の使用
Implクラスを、
– Widgetクラスの内側で動的に確保して
– Widgetクラスと同時に破棄
するなら、std::unique_ptr がよい
class Widget {
public:
Widget();
…
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
};
[widget.h]
…
struct Widget::Impl {
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
Widget::Widget()
: pImpl(std::make_unique<Impl>()) { }
// no destructor
[widget.cpp]
http://bit.ly/1KjcGeb
std::unique_ptr の問題点 (1)
クライアントコードがコンパイルできない!
#include “widget.h”
Widget w;
In file included from client.cpp:1:
In file included from ./widget.hpp:1:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:2
incomplete type 'Widget::Impl'
static_assert(sizeof(_Tp) > 0, "default_delete can not delete incomplete type");
^~~~~~~~~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:2
'std::__1::default_delete<Widget::Impl>::operator()' requested here
__ptr_.second()(__tmp);
^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:2
'std::__1::unique_ptr<Widget::Impl, std::__1::default_delete<Widget::Impl> >::reset' requested here
_LIBCPP_INLINE_VISIBILITY ~unique_ptr() {reset();}
^
./widget.hpp:3:7: note: in instantiation of member function 'std::__1::unique_ptr<Widget::Impl, std::__1::default
class Widget {
^
./widget.hpp:8:10: note: forward declaration of 'Widget::Impl'
struct Impl;
^
1 error generated.
LLVM3.6-svn0
http://bit.ly/1KjcGeb
std::unique_ptr の問題点 (1)
え!?
– std::unique_ptrはincomplete type でも使えると言わ
れている [*]
– pImplイディオムこそ、std::unique_ptrを一番使いた
いところ
幸い、解決はそれほど難しくない
[*] http://howardhinnant.github.io/incomplete.html
http://bit.ly/1KjcGeb
std::unique_ptrで何が起こっているか?
エラーの原因:
1. unique_ptrを使ったので、Widgetのデストラクタを省略
した
2. コンパイラがdtorを自動生成、メンバ pImpl のdtorが自
動で呼ばれる
3. pImpl のdtorは、unique_ptrのdefault deleter
4. 生ポインタに対して delete が呼ばれるが、その前に
static_assert を使って、ポインタがincomplete type
で無いことをチェックしている
5. → エラー
http://bit.ly/1KjcGeb
解決法
widgetのdtorから、Implクラスが見えるようにすれば
良い
class Widget {
public:
Widget();
~Widget();
…
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
};
[widget.h]
…
struct Widget::Impl {
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
Widget::Widget()
: pImpl(std::make_unique<Impl>()) { }
Widget::~Widget() { }
[widget.cpp]
http://bit.ly/1KjcGeb
解決法
widgetのdtorから、Implクラスが見えるようにすれば
良い
– =default も使える
class Widget {
public:
Widget();
~Widget();
…
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
};
[widget.h]
…
struct Widget::Impl {
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
Widget::Widget()
: pImpl(std::make_unique<Impl>()) { }
Widget::~Widget() = default;
[widget.cpp]
http://bit.ly/1KjcGeb
move操作のサポート
std::unique_ptr を使っているので、Widgetクラスはmove
セマンティクスをサポートするのが自然
dtorを自分で宣言したので、move操作は自動生成されない
(Item17参照) → 自分で宣言しましょう
class Widget {
public:
Widget();
~Widget();
Widget(Widget&&rhs);
Widget& operator=(const Widget&& rhs)
…
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
};
[widget.h]
…
Widget::Widget(Widget&& rhs) = default;
Widget& Widget(const Widget&& rhs) = default;
…
[widget.cpp]
※ヘッダ中に定義を書いてしまうと
前と同じエラーになるので注意
(move ctor中で例外が発生した場合に
dtorを呼ぶ必要があるため)http://bit.ly/1KjcGeb
コピー操作のサポート
Widgetクラスは、内部にvector, string, Gadgetを含んでいるので、
もしGadgetがコピー可能であれば、Widgetもコピー可能である
はずだが、
1. std::unique_ptr は move-only
2. 仮にcopyをサポートしたとしても shallow-copy しかされない
→ やりたいのは(普通は) deep-copy
→自分で書く必要がある
class Widget {
…
Widget(Widget& rhs);
Widget& operator=(const Widget& rhs)
…
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
};
[widget.h]
…
// Copy ctor
Widget::Widget(Widget& rhs)
: pImpl(std:: make_unique<Impl>(*rhs.pImpl
// copy operator
Widget& Widget(const Widget& rhs) {
*pImpl = *rhs.pImpl;
return *this;
}
…
[widget.cpp]
http://bit.ly/1KjcGeb
std::shared_ptrの利用
std::unique_ptr の代わりに std::shared_ptr を使うと、このような
一連の問題は起こらず、全てうまくいく
(move ctor等も全て自動生成でうまくいく)
custom deleter をサポートするための実装方法の違いによる
unique_ptr ・・・ deleterの型がsmart pointerの型の一部
→より小さく速いコードを出力できる可能性があ
るが、コンパイラが特殊関数を自動生成するた
めにはcomplete typeでなければならない
shared_ptr ・・・ deleterの型がsmart pointerの型の一部でない
→実行時のオーバーヘッドがあるが、complete
typeである必要が無い
http://bit.ly/1KjcGeb
Things to remember
• Pimplイディオムは、コンパイル時の依存関係を減ら
し、ビルド時間を短縮することができる
• Pimplイディオムでunique_ptrを使うときは、特殊メ
ンバ関数をヘッダーで宣言し、実装ファイルで定義
しよう(内容はデフォルト関数で問題無いとしても)
• shared_ptrの場合はこのような考慮は不要
http://bit.ly/1KjcGeb

Effective Modern C++ 勉強会 Item 22