effective modern c++勉強会#4 item 17, 18資料

29
Effective Modern C++勉強会#4 Item 17,18 東京大学物性研究所 特任研究員 五十嵐 亮(@rigarash) この資料は http ://1drv.ms/1DLhxPS にあります。

Upload: rigarash

Post on 22-Jul-2015

102 views

Category:

Software


3 download

TRANSCRIPT

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

Effective Modern C++勉強会#4

Item 17,18東京大学物性研究所 特任研究員

五十嵐 亮(@rigarash)

この資料は http://1drv.ms/1DLhxPS にあります。

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

Item17:特殊メンバ関数の生成について理解しよう1: 概要

特殊メンバ関数(special member function)とは?

C++で勝手に(暗黙に)生成されても構わない関数

C++98では4つのみ

デフォルトコンストラクタ

デストラクタ

コピーコンストラクタ

コピー代入演算子

C++11で2つ追加

ムーブコンストラクタ

ムーブ代入演算子

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

Item 17:特殊メンバ関数の生成について理解しよう2: C++98での注意点

必要になったときにのみ生成される

暗黙にpublicでinlineでnonvirtual(例外あり)

基底クラスのデストラクタがvirtualならvirtual

デフォルトコンストラクタ

クラスにコンストラクタが一つも定義されてないときのみ生成される

引数のあるコンストラクタを定義したときには生成されない

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

Item 17:特殊メンバ関数の生成について理解しよう3: C++11で追加された関数

ムーブコンストラクタとムーブ代入演算子

生成ルールと振る舞いはコピーと同等

必要となった時に生成

非静的(non-static)メンバは個別にムーブ(std::move)

基底クラスの非静的メンバも(あれば)ムーブ生成・代入

class Widget { public:...Widget(Widget&& rhs); // ムーブコンストラクタWidget& operator=(Widget&& rhs); // ムーブ代入演算子

...}

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

ムーブ操作(ムーブコンストラクタとムーブ代入演算子)

ムーブが実際に行われる保証はない

ムーブの要請

ムーブ操作のサポートがなければコピーを利用する

関数オーバーロードで決定(std::move()を使うため)

cf. Item 23

Item 17:特殊メンバ関数の生成について理解しよう4: ムーブ操作

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

コピーコンストラクタとコピー代入演算子

互いに独立(どちらかのみ宣言してももう一方は自動生成)

後方互換性のためこの挙動は残る

ムーブ操作の宣言で自動生成は抑制

一見C++98より制限が厳しくなったようにみえるが、C++98にはムーブ操作は存在しないため問題ない

ムーブコンストラクタとムーブ代入演算子

互いに独立ではない

どちらかを宣言すると、もう片方の自動生成は抑制

コピー操作、デストラクタの宣言で自動生成は抑制

Item 17:特殊メンバ関数の生成について理解しよう5: ムーブ操作とコピー操作の関係

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

「コピーコンストラクタ、コピー代入演算子、デストラクタのどれかを宣言するなら、3つとも宣言するべき」

C++98からの有名なガイドライン

コピー操作の必要性はリソース管理に由来する

1. あるコピー操作でのリソース管理はもう一方でも必要

2. デストラクタはリソース管理(資源の開放)を行う

特にリソース管理としての「メモリ」

メモリ管理を行う標準ライブラリはすべて3つ宣言

動的にメモリを管理するSTLコンテナなど

Item 17:特殊メンバ関数の生成について理解しよう

6: Rule of Three

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

自動生成のコピー操作が正しいときに明示的に指定する

多相的な基底クラスで有用

一般に仮想デストラクタを持つ

基底クラスのポインタを介したdelete, typeidなどが未定義

自動生成のコピー操作の振る舞いは正しいことが多い

Item 17:特殊メンバ関数の生成について理解しよう

7: =default

class Widget {public:...~Widget(); // ユーザ定義デストラクタ

...Widget(const Widget&) = default; Widget& operator=(const Widget&) = default; …

};

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

仮想デストラクタでも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;

...};

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

StringTableの宣言

次のように変えると…

Item 17:特殊メンバ関数の生成について理解しよう9: StringTableの例

class StringTable { public:

StringTable() {} ... // copy/move/dtor以外の関数

// 例えばinsert, erase, lookup

private:std::map<int, std::string> values;

};

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

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;

};

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

ユーザー関数テンプレートは自動生成規則に影響しない

次の例ではTがWidgetでも自動生成される

Item 17:特殊メンバ関数の生成について理解しよう11: ユーザー関数テンプレート

class Widget { template<typename T> Widget(const T& rhs); template<typename T> Widget& operator=(const T& rhs);

};

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

デフォルトコンストラクタ

C++98と同じ

デストラクタ

C++98とほぼ同じ

デフォルトでnoexceptになった

Item 17:特殊メンバ関数の生成について理解しようC++11ルールのまとめ

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

コピーコンストラクタ

実行時の振る舞いはC++98と同じ

ユーザー定義コピー代入演算子かデストラクタの存在時の自動生成に依存するコードは推奨されない

コピー代入演算子

実行時の振る舞いはC++98と同じ

ユーザー定義コピーコンストラクタかデストラクタの存在時の自動生成に依存するコードは推奨されない

Item 17:特殊メンバ関数の生成について理解しようC++11ルールのまとめ

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

ムーブコンストラクタとムーブ代入演算子

非静的メンバのメンバ別ムーブ

コピー操作、ムーブ操作、デストラクタがない場合のみ生成される

Item 17:特殊メンバ関数の生成について理解しようC++11ルールのまとめ

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

特殊メンバ関数はそれ自身の明示的な宣言がないときコンパイラが暗黙に生成するもの

デフォルトコンストラクタ

デストラクタ

コピー操作(コピーコンストラクタとコピー代入演算子)

ムーブ操作(ムーブコンストラクタとムーブ代入演算子)

ムーブ操作はコピー操作、デストラクタが明示的に宣言されてないときのみ生成される

コピーコンストラクタはムーブ操作が宣言されるとdeleteされる

デストラクタの明示的な宣言があるときのコピー操作の生成は推奨されない

メンバ関数テンプレートは特殊メンバ関数の生成を止めない

Item 17:特殊メンバ関数の生成について理解しよう

Things to Remember

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

スマートポインタ

生ポインタの問題点

1. 指す先がオブジェクトか配列かわからない

2. 使い終わったあと破棄すべきかわからない

3. どのように破棄すべきかわからない

deleteや破棄専用関数

4. deleteだとして、deleteとdelete[]のどちらかわからない

5. 一度だけ確実に破棄するのが難しい

例外を含めて多数のコードパスがある

6. ダングリングポインタかどうかわからない

破棄されたけオブジェクトを指し示すポインタ

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

スマートポインタ

スマートポインタとは

生ポインタのラッパーで、落とし穴を避けるためのもの

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の解説ではなくスマートポインタを効果的に使うための情報のまとめ

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

std::unique_ptr

生ポインタと同じサイズ、(ほとんどの場合)同じ命令

メモリとCPU資源に乏しいときにもOK

排他的な所有権(exclusive ownership)を表現

std::unique_ptrがnon-null

所有権を有する

Item 18:専有リソースにはstd::unique_ptrを使おう

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

std::unique_ptrの移動

所有権の移動(移動元はnullに)

std::unique_ptrのコピー

禁止(排他的な所有権の複製はできない)

std::unique_ptrはmove-onlyな型

std::unique_ptrのデストラクト

所有権のあるリソースの破棄

所有する生ポインタをdelete

Item 18:専有リソースにはstd::unique_ptrを使おう

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

クラス階層があるオブジェクトをファクトリ関数へ

次のような関数から利用

利用方法

Investme

nt

Bo

nd

Sto

ck

RealEs

tate

template<typename... Ts> // return std::unique_ptrstd::unique_ptr<Investment> // to an object createdmakeInvestment(Ts&&... params); // from the given args

{ ...auto pInvestment = // pInvestmentは

makeInvestment( arguments ); // std::unique_ptr<Investment>...

} // *pInvestmentをデストラクト

Item 18:専有リソースにはstd::unique_ptrを使おう典型的な利用例

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

所有権の移譲シナリオでも利用できる

vec.push_back(makeInvestment( arg ));

コンテナが破棄されるときにunique_ptrも破棄される

以下の場合を除き例外が飛んだときでも破棄される

例外がエントリポイントの外まで伝播する

noexceptの仕様に反する

std::abortやstd::exitなどの終了関数が呼ばれる

Item 18:専有リソースにはstd::unique_ptrを使おう典型的な利用例

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

デフォルトでは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);...

}

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

ラムダ式でのカスタムデリータ

関数よりも効率がよい

カスタムデリータの型をテンプレートの第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)...)); }

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

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);

...}

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

デフォルトのデリータ(delete)なら生ポインタと同じ

関数ポインタの場合

1-2 word分増加

関数オブジェクトの場合

ステートレス(キャプチャなしラムダ式など)ならペナルティなし

Item 18:専有リソースにはstd::unique_ptrを使おうカスタムデリータのメモリ使用量

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

キャプチャなしラムダ式なら必要ないコストがかかる

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を使おうカスタムデリータのメモリ使用量

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

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を使おうその他

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

std::unique_ptrは以下の特徴を持つスマートポインタ

小さい

速い

ムーブ専用

専有リソースの管理

デフォルトのデリータはdeleteだが、カスタムも可能

ステートフルなデリータと関数ポインタはサイズ増加

std::unique_ptrはstd::shared_ptrに簡単に変換できる

Item 18:専有リソースにはstd::unique_ptrを使おう

Things to Remember