SlideShare a Scribd company logo
Effective Modern
C++勉強会#4
Item 17,18
東京大学物性研究所 特任研究員
五十嵐 亮(@rigarash)
この資料は http://1drv.ms/1DLhxPS にあります。
Item17:
特殊メンバ関数の生成について理解しよう
1: 概要
 特殊メンバ関数(special member function)とは?
 C++で勝手に(暗黙に)生成されても構わない関数
 C++98では4つのみ
 デフォルトコンストラクタ
 デストラクタ
 コピーコンストラクタ
 コピー代入演算子
 C++11で2つ追加
 ムーブコンストラクタ
 ムーブ代入演算子
Item 17:
特殊メンバ関数の生成について理解しよう
2: C++98での注意点
 必要になったときにのみ生成される
 暗黙にpublicでinlineでnonvirtual(例外あり)
 基底クラスのデストラクタがvirtualならvirtual
 デフォルトコンストラクタ
 クラスにコンストラクタが一つも定義されてないときのみ
生成される
 引数のあるコンストラクタを定義したときには生成されない
Item 17:
特殊メンバ関数の生成について理解しよう
3: C++11で追加された関数
 ムーブコンストラクタとムーブ代入演算子
 生成ルールと振る舞いはコピーと同等
 必要となった時に生成
 非静的(non-static)メンバは個別にムーブ(std::move)
 基底クラスの非静的メンバも(あれば)ムーブ生成・代入
class Widget {
public:
...
Widget(Widget&& rhs); // ムーブコンストラクタ
Widget& operator=(Widget&& rhs); // ムーブ代入演算子
...
}
 ムーブ操作(ムーブコンストラクタとムーブ代入演算子)
 ムーブが実際に行われる保証はない
 ムーブの要請
 ムーブ操作のサポートがなければコピーを利用する
 関数オーバーロードで決定(std::move()を使うため)
 cf. Item 23
Item 17:
特殊メンバ関数の生成について理解しよう
4: ムーブ操作
 コピーコンストラクタとコピー代入演算子
 互いに独立(どちらかのみ宣言してももう一方は自動生成)
 後方互換性のためこの挙動は残る
 ムーブ操作の宣言で自動生成は抑制
 一見C++98より制限が厳しくなったようにみえるが、C++98に
はムーブ操作は存在しないため問題ない
 ムーブコンストラクタとムーブ代入演算子
 互いに独立ではない
 どちらかを宣言すると、もう片方の自動生成は抑制
 コピー操作、デストラクタの宣言で自動生成は抑制
Item 17:
特殊メンバ関数の生成について理解しよう
5: ムーブ操作とコピー操作の関係
「コピーコンストラクタ、コピー代入演算子、デストラク
タのどれかを宣言するなら、3つとも宣言するべき」
 C++98からの有名なガイドライン
 コピー操作の必要性はリソース管理に由来する
1. あるコピー操作でのリソース管理はもう一方でも必要
2. デストラクタはリソース管理(資源の開放)を行う
 特にリソース管理としての「メモリ」
 メモリ管理を行う標準ライブラリはすべて3つ宣言
 動的にメモリを管理するSTLコンテナなど
Item 17:
特殊メンバ関数の生成について理解しよう
6: Rule of Three
 自動生成のコピー操作が正しいときに明示的に指定する
 多相的な基底クラスで有用
 一般に仮想デストラクタを持つ
 基底クラスのポインタを介したdelete, typeidなどが未定義
 自動生成のコピー操作の振る舞いは正しいことが多い
Item 17:
特殊メンバ関数の生成について理解しよう
7: =default
class Widget {
public:
...
~Widget(); // ユーザ定義デストラクタ
...
Widget(const Widget&) = default;
Widget& operator=(const Widget&) = default;
…
};
 仮想デストラクタでもOK
Item 17:
特殊メンバ関数の生成について理解しよう
8: =default 2
class Base {
public:
virtual ~Base() = default; // デストラクタをvirtualに
Base(Base&&) = default; // ムーブをサポート
Base& operator=(Base&&) = default;
Base(const Base&) = default; // コピーをサポート
Base& operator=(const Base&) = default;
...
};
 StringTableの宣言
 次のように変えると…
Item 17:
特殊メンバ関数の生成について理解しよう
9: StringTableの例
class StringTable {
public:
StringTable() {}
... // copy/move/dtor以外の関数
// 例えばinsert, erase, lookup
private:
std::map<int, std::string> values;
};
 StringTableの例
 ムーブ操作が自動生成されない!
 std::mapのコピーはムーブよりとても遅い
 明示的に=defaultを書く習慣をつけてもよい
Item 17:
特殊メンバ関数の生成について理解しよう
10: StringTableの例 2
class StringTable {
public:
StringTable()
{ makeLogEntry("Creating StringTable object"); } // 追加
~StringTable()
{ makeLogEntry("Destroying StringTable object"); } // 追加
... // move以外の関数
// 例えばinsert, erase, lookup
private:
std::map<int, std::string> values;
};
 ユーザー関数テンプレートは自動生成規則に影響しない
 次の例ではTがWidgetでも自動生成される
Item 17:
特殊メンバ関数の生成について理解しよう
11: ユーザー関数テンプレート
class Widget {
template<typename T> Widget(const T& rhs);
template<typename T> Widget& operator=(const T& rhs);
};
 デフォルトコンストラクタ
 C++98と同じ
 デストラクタ
 C++98とほぼ同じ
 デフォルトでnoexceptになった
Item 17:
特殊メンバ関数の生成について理解しよう
C++11ルールのまとめ
 コピーコンストラクタ
 実行時の振る舞いはC++98と同じ
 ユーザー定義コピー代入演算子かデストラクタの存在時の
自動生成に依存するコードは推奨されない
 コピー代入演算子
 実行時の振る舞いはC++98と同じ
 ユーザー定義コピーコンストラクタかデストラクタの存在
時の自動生成に依存するコードは推奨されない
Item 17:
特殊メンバ関数の生成について理解しよう
C++11ルールのまとめ
 ムーブコンストラクタとムーブ代入演算子
 非静的メンバのメンバ別ムーブ
 コピー操作、ムーブ操作、デストラクタがない場合のみ生
成される
Item 17:
特殊メンバ関数の生成について理解しよう
C++11ルールのまとめ
 特殊メンバ関数はそれ自身の明示的な宣言がないときコンパイラが
暗黙に生成するもの
 デフォルトコンストラクタ
 デストラクタ
 コピー操作(コピーコンストラクタとコピー代入演算子)
 ムーブ操作(ムーブコンストラクタとムーブ代入演算子)
 ムーブ操作はコピー操作、デストラクタが明示的に宣言されてない
ときのみ生成される
 コピーコンストラクタはムーブ操作が宣言されるとdeleteされる
 デストラクタの明示的な宣言があるときのコピー操作の生成は推奨
されない
 メンバ関数テンプレートは特殊メンバ関数の生成を止めない
Item 17:
特殊メンバ関数の生成について理解しよう
Things to Remember
スマートポインタ
 生ポインタの問題点
1. 指す先がオブジェクトか配列かわからない
2. 使い終わったあと破棄すべきかわからない
3. どのように破棄すべきかわからない
 deleteや破棄専用関数
4. deleteだとして、deleteとdelete[]のどちらかわからない
5. 一度だけ確実に破棄するのが難しい
 例外を含めて多数のコードパスがある
6. ダングリングポインタかどうかわからない
 破棄されたけオブジェクトを指し示すポインタ
スマートポインタ
 スマートポインタとは
 生ポインタのラッパーで、落とし穴を避けるためのもの
 C++11でのスマートポインタ
 std::auto_ptr (C++98, 非推奨)
 C++98のコンパイラを使う必要がない限り使わないこと
 std::unique_ptr (C++11, Item 18)
 std::shared_ptr (C++11, Item 19)
 std::weak_ptr (C++11, Item 20)
 APIの解説ではなくスマートポインタを効果的に使うため
の情報のまとめ
 std::unique_ptr
 生ポインタと同じサイズ、(ほとんどの場合)同じ命令
 メモリとCPU資源に乏しいときにもOK
 排他的な所有権(exclusive ownership)を表現
 std::unique_ptrがnon-null
 所有権を有する
Item 18:
専有リソースにはstd::unique_ptrを使おう
 std::unique_ptrの移動
 所有権の移動(移動元はnullに)
 std::unique_ptrのコピー
 禁止(排他的な所有権の複製はできない)
 std::unique_ptrはmove-onlyな型
 std::unique_ptrのデストラクト
 所有権のあるリソースの破棄
 所有する生ポインタをdelete
Item 18:
専有リソースにはstd::unique_ptrを使おう
 クラス階層があるオブジェクトをファクトリ関数へ
 次のような関数から利用
 利用方法
Investme
nt
Bo
nd
Sto
ck
RealEs
tate
template<typename... Ts> // return std::unique_ptr
std::unique_ptr<Investment> // to an object created
makeInvestment(Ts&&... params); // from the given args
{
...
auto pInvestment = // pInvestmentは
makeInvestment( arguments ); // std::unique_ptr<Investment>
...
} // *pInvestmentをデストラクト
Item 18:
専有リソースにはstd::unique_ptrを使おう
典型的な利用例
 所有権の移譲シナリオでも利用できる
 vec.push_back(makeInvestment( arg ));
 コンテナが破棄されるときにunique_ptrも破棄される
 以下の場合を除き例外が飛んだときでも破棄される
 例外がエントリポイントの外まで伝播する
 noexceptの仕様に反する
 std::abortやstd::exitなどの終了関数が呼ばれる
Item 18:
専有リソースにはstd::unique_ptrを使おう
典型的な利用例
 デフォルトではdeleteを呼ぶ
 任意の関数、あるいは関数オブジェクト(ラムダ式含む)
もカスタムデリータにすることができる
 破壊時にログを出力する例
Item 18:
専有リソースにはstd::unique_ptrを使おう
カスタムデリータ
// ラムダ式のカスタムデリータ
auto delInvmt = [](Investment* pInvestment)
{
makeLogEntry(pInvestment);
delete pInvestment;
};
template<typename... Ts>
std::unique_ptr<Investment, decltype(delInvmt)>
makeInvestment(Ts&&... params)
{
std::unique_ptr<Investment, decltype(delInvmt)>
pInv(nullptr, delInvmt);
...
}
 ラムダ式でのカスタムデリータ
 関数よりも効率がよい
 カスタムデリータの型をテンプレートの第2引数に
 decltype(Item 3)を利用するとよい
 カスタムデリータをコンストラクタの第2引数に
 生ポインタを代入できない
 代わりにpInv.reset()を使う
 引数を渡すにはstd::forward(Item 25)を使う
Item 18:
専有リソースにはstd::unique_ptrを使おう
カスタムデリータ
if ( ... )
{
pInv.reset(new Stock(std::forward<Ts>(params)...));
}
 function return type deduction(Item 3)がある
Item 18:
専有リソースにはstd::unique_ptrを使おう
C++14
template<typename... Ts>
auto makeInvestment(Ts&&... params)
{
// ラムダ式のカスタムデリータ; 関数内に定義
auto delInvmt = [](Investment* pInvestment)
{
makeLogEntry(pInvestment);
delete pInvestment;
};
std::unique_ptr<Investment, decltype(delInvmt)>
pInv(nullptr, delInvmt);
...
}
 デフォルトのデリータ(delete)なら生ポインタと同じ
 関数ポインタの場合
 1-2 word分増加
 関数オブジェクトの場合
 ステートレス(キャプチャなしラムダ式など)ならペナル
ティなし
Item 18:
専有リソースにはstd::unique_ptrを使おう
カスタムデリータのメモリ使用量
 キャプチャなしラムダ式なら必要ないコストがかかる
void delInvmt(Investment* pInvestment)
{
makeLogEntry(pInvestment);
delete pInvestment;
}
// Investment* と関数ポインタが必要
template<typename... Ts>
std::unique_ptr<Investment,
void (*)(Investment*)>
makeInvestment(Ts&&... params);
Item 18:
専有リソースにはstd::unique_ptrを使おう
カスタムデリータのメモリ使用量
 Pimplイディオム(Item 22)でも利用される
 配列用にstd::unique_ptr<T[]>もある
 operator[]はあるがoperator*, operator->はない
 C APIのようなものを使うとき以外はarray, vector, stringが
ベター
 std::shared_ptrに代入して変換できる
 Factory関数の返り値にはshared_ptrよりもよい
Item 18:
専有リソースにはstd::unique_ptrを使おう
その他
 std::unique_ptrは以下の特徴を持つスマートポインタ
 小さい
 速い
 ムーブ専用
 専有リソースの管理
 デフォルトのデリータはdeleteだが、カスタムも可能
 ステートフルなデリータと関数ポインタはサイズ増加
 std::unique_ptrはstd::shared_ptrに簡単に変換できる
Item 18:
専有リソースにはstd::unique_ptrを使おう
Things to Remember

More Related Content

Effective Modern C++勉強会#4 Item 17, 18資料