24
Effective Modern C++ 読書会#2 Item7,8 © 2015 sprout Inc. All Rights Reserved. 1 株式会社スプラウト 高橋 Twitter : @Talos208

Effective Modern C++ Item 7&8

  • Upload
    -

  • View
    66

  • Download
    3

Embed Size (px)

Citation preview

Page 1: Effective Modern C++ Item 7&8

Effective Modern C++読書会#2

Item7,8

© 2015 sprout Inc. All Rights Reserved.1

株式会社スプラウト高橋 明Twitter : @Talos208

Page 2: Effective Modern C++ Item 7&8

Item7

Distinguish between () and {}

when creating objects.

© 2015 sprout Inc. All Rights Reserved.2

Page 3: Effective Modern C++ Item 7&8

C++11の初期化方法

© 2015 sprout Inc. All Rights Reserved.3

int x( 0 ); // (1)括弧で囲った初期化子

int y = 0; // (2) "="に続く初期化子

int z{ 0 }; // (3) 中括弧で囲った初期化子

int z = { 0 }; // (4) "="に続き、中括弧で囲った初期化子

(4)は(3)と同じに扱われる。

Page 4: Effective Modern C++ Item 7&8

初期化時の“=”

• 初期化時の“=”は代入ではない

• C++初心者が間違えやすい点

© 2015 sprout Inc. All Rights Reserved.4

Widget w1; // デフォルトコンストラクタの呼び出し

Widget w2 = w1; // 代入ではなく、コピーコンストラクタの呼び出し

w1 = w2; // 代入。operator()が呼び出される

Page 5: Effective Modern C++ Item 7&8

「統一的な初期化構文」

// 複数要素の代入

std::vector<int> v{1,3,5}; // 1,3,5の3要素を格納したvector

// メンバ変数の初期化

class Widget {

...

private:

int x{ 0 }; // x を初期値0で初期化

int y = 0; // y を初期値0で初期化

int z(0); // エラー!

}

// コピーできないオブジェクト(例:std::atmic)には“=”が使えない

std::atomic<int> ai0{ 0 }; // OK

std::atomic<int> ai1(0); // OK

std::atomic<int> ai2 = 0; // エラー!

© 2015 sprout Inc. All Rights Reserved.5

Page 6: Effective Modern C++ Item 7&8

“{}”内でのnarrowingの禁止

double x,y,z;

...

int sum1{ x + y + z }; // エラー。doubleはintで表せない場合がある

int sum2( x + y + z ); // OK。暗黙の型変換でintになる

int sum3 = x + y + z; // 同上

© 2015 sprout Inc. All Rights Reserved.6

後者2つの挙動を変えると、あまりに多くのレガシーコードを破壊してしまう。

Page 7: Effective Modern C++ Item 7&8

「Most Vexing Parse (最も厄介な解析)」

Widget w1(10); // 10を引数としたWidget型変数の初期化

// 同様に引数なしの初期化を意図して次のように書くと、

// コンパイラに誤って解釈される

Widget w2(); // Widgetを返す関数w2の宣言に解釈される

// 以下のように記述すれば意図通り

Widget w3{}; // 引数なしでのWidget型変数の初期化

Widget w4; // これで良くない?

© 2015 sprout Inc. All Rights Reserved.7

Page 8: Effective Modern C++ Item 7&8

std::initializer_listとコンストラクタ

class Widget {

public:

Widget(int i, bool b);

Widget(int i, double d);

...

};

Widget w1(10, true); // 最初のコンストラクタが呼ばれる

Widget w2{10, true}; // 同上

Widget w3(10, 5.0); // 2番目のコンストラクタが呼ばれる

Widget w4{10, 5.0}; // 同上

© 2015 sprout Inc. All Rights Reserved.8

Page 9: Effective Modern C++ Item 7&8

std::initializer_listとコンストラクタ(2)

class Widget {

public:

Widget(int i, bool b);

Widget(int i, double d);

Widget(std::initializer_list<long double> il); // これを追加

...

};

Widget w1(10, true); // 変化なし

Widget w2{10, true}; // 3番目のコンストラクタが呼ばれる!

// 10とtrueの両方がlong doubleにキャスト

Widget w3(10, 5.0); // 変化なし

Widget w4{10, 5.0}; // 3番目のコンストラクタが呼ばれる!

// 10と5.0の両方がlong doubleにキャスト

© 2015 sprout Inc. All Rights Reserved.9

Page 10: Effective Modern C++ Item 7&8

コピー/ムーブのコンストラクタ

class Widget {

public:

Widget(int i, bool b);

Widget(int i, double d);

Widget(std::initializer_list<long double> il);

operator float() const; // floatへの暗黙の型変換

...

};

Widget w4;

Widget w5(w4); // w4からのコピーコンストラクタが呼ばれる

Widget w6{w4}; // 3番目のコンストラクタが呼ばれる!

// w4がfloatにキャストされ、さらにlong doubleに変換

Widget w7(std::move(w4)); // w4からのムーブコンストラクタが呼ばれる

Widget w8{std::move(w4)}; // 3番目のコンストラクタが呼ばれる!

© 2015 sprout Inc. All Rights Reserved.10

Page 11: Effective Modern C++ Item 7&8

std::initializer_listの優先度

class Widget {

public:

Widget(int i, bool b);

Widget(int i, double d);

Widget(std::initializer_list<bool> il);

// bool型のinitializer_listのコンストラクタ

// 暗黙の型変換を行うメソッドはない

...

};

Widget w4{10, 5.0}; // エラー!

© 2015 sprout Inc. All Rights Reserved.11

コンパイラは(他のコンストラクタに優先して)std::initializer_list<bool>を呼ぼうとしてint(10)とdouble(5.0)をboolに変換する(intまたはdoubleからboolへの暗黙の変換はできる)この変換はnarrowingなので“{}”の中ではできず、エラーとなる。

Page 12: Effective Modern C++ Item 7&8

std::initializer_list<T>のTへの変換がそもそもできない場合

class Widget {

public:

Widget(int i, bool b);

Widget(int i, double d);

Widget(std::initializer_list<std::string> il);

// boolからstd::stringに変更

...

};

Widget w1(10, true); // 最初のコンストラクタが呼ばれる

Widget w2{10, true}; // 最初のコンストラクタが呼ばれるようになる

Widget w3(10, 5.0); // 2番目のコンストラクタが呼ばれる

Widget w4{10, 5.0}; // 2番目のコンストラクタが呼ばれるようになる

© 2015 sprout Inc. All Rights Reserved.12

Page 13: Effective Modern C++ Item 7&8

初期化子のない“{}”

class Widget {

public:

Widget();

Widget(std::initializer_list<int> il); // boolからstd::stringに変更

...

};

Widget w1; // デフォルトコンストラクタが呼ばれる

Widget w2{}; // 同様にデフォルトコンストラクタが呼ばれる

Widget w3(); // 最も厄介な解析! 関数宣言になる

// 0要素のstd::initializer_list<>として呼びたいなら“()”か“{}”で囲む

Widget w4({}); // std::initializer_list<int>のコンストラクタが呼ばれる

Widget w5{{}}; // 同上

© 2015 sprout Inc. All Rights Reserved.13

0要素のstd::initializer_listでなく、引数がないことを表す

Page 14: Effective Modern C++ Item 7&8

std::vector<T>とstd::initializer_list

std::vector<int> v1(10,20);

// サイズと初期値を指定するコンストラクタが呼ばれる。

// 各要素が20で初期化された、10要素のvectorができる

std::vector<int> v2{10,20};

// std::initializer_list<int>のコンストラクタが呼ばれる。

// 10と20で初期化された、2要素のvectorができる

© 2015 sprout Inc. All Rights Reserved.14

• それまでstd::initializer_list<>を引数に持つコンストラクタがないクラスに、それを引数に持つコンストラクタを1つでも加えると、互換性を破壊するおそれがある。

• クラスの提供者は、クラスにstd::initializer_list<>を引数に持つコンストラクタを加えるとき、熟考が必要。

• クラスの使用者も、初期化時に"()”と“{}”のどちらを使うか慎重に検討する必要がある。

Page 15: Effective Modern C++ Item 7&8

Pros and Cons

• “{}”の使用

• 適用可能なケースが多い

• 間違えてnarrowingしてしまう可能性が低い

• Most Vexing Parseの心配がない

• “()”の使用

• C++98の文法と互換

• std::initializer_listに関する問題がない

© 2015 sprout Inc. All Rights Reserved.15

Page 16: Effective Modern C++ Item 7&8

テンプレート

// paramsを引数としてローカルのT型のオブジェクトを作成

template<typename T, // 作成するオブジェクトの型

typename... Ts> // 使用する引数の型

void doSomeWork(Ts&&... params){

T localObject(std::forward<Ts>(params...)); // “()”を使う場合

// T localObject{std::forward<Ts>(params...)}; // “{}”を使う場合

}

Std::vector<int> v;

doSomeWork<std::vector<int>>(10,20); // 10要素のvectorが正しいのか?

// 2要素のvectorが正しいのか?

© 2015 sprout Inc. All Rights Reserved.16

doSomeWorkの結果として何が正しいのか、doSomeWorkを書く側では判らない。呼び出し側に依存する。Std::make_uniqueやstd::make_shareでは、内部で“()”を使用して、それをドキュメントに記載している。

Page 17: Effective Modern C++ Item 7&8

Things to Remember

• “{}”による初期化は、適用可能なケースが多く、narrowing変換を防げ、C++のMost Vexing Parseの心配がない

• コンストラクタオーバーロードの解決において、“{}”による初期化は、他にもっと適したコンストラクタがあっても、可能であればstd::initializer_listに割り当てられてしまう

• “()”と“{}”とで大きな違いを産んでしまう例の一つが、std::vector<数値型>のコンストラクタを2つの引数で呼ぶ場合である

• テンプレート内部で“()”と“{}”のどちらを使うかは難しい問題だ

© 2015 sprout Inc. All Rights Reserved.17

Page 18: Effective Modern C++ Item 7&8

Item 8

Prefer nullptr to 0 and NULL.

© 2015 sprout Inc. All Rights Reserved.18

Page 19: Effective Modern C++ Item 7&8

0とNULLとnullptr

• C++ではポインタが使えるところに0があった場合、ヌルポインタにフォールバックするが、リテラルの0はintであってポインタではない。NULLもほぼ同様だが、int以外の整数型としても使えるところが違う。

• リテラルとしてのNULLに決まった型はない。C++98では、ポインタと整数型のオーバーロードに0やNULLを渡した場合、ポインタのオーバーロードは呼ばれない。

• NULLが、例えば0Lと定義されていた場合、long→int、long→bool、0L→void*のいずれの変換も同様に正しいので、どれかを選びようがない。

• nullptrの型はstd::nullptr_tで、すべてのポインタ型に変換可能

© 2015 sprout Inc. All Rights Reserved.19

void f(int);

void f(bool);

void f(void*);

f(0); // f(void*)でなくf(int)が呼ばれる

f(NULL); // ビルドエラーになるかもしれないが、たいていは

// f(int)が呼ばれる。f(void*)は呼ばれない

f(nullptr); // f(void*)が呼ばれる

Page 20: Effective Modern C++ Item 7&8

Nullptrより0がよい場合

auto result = findRecord( /* ここに引数 */ );

if (result == 0) {

...

}

© 2015 sprout Inc. All Rights Reserved.20

resultの型がわからなくても、0ならば整数型でもポインタでもどちらでもOK

Page 21: Effective Modern C++ Item 7&8

テンプレートとnullptr

• ミューテックスでガードしなければならない関数f1、f2、f3がある。単純に書くと

© 2015 sprout Inc. All Rights Reserved.21

int f1(std::shared_ptr<Widget> spw);

int f2(std::unique_ptr<Widget> spw);

int f3(*Widget spw);

std::mutex f1m, f2m, f3m;

using MuxGuard = std::lock_guard<std::mutex>;

{

MutexGuard g(f1m); // ロック

auto result = f1(0);

// スコープを抜けるところでロック解放

}

...

{

MutexGuard g(f2m);

auto result = f1(NULL);

}

...

{

MutexGuard g(f3m);

auto result = f3(nullptr);

}

Page 22: Effective Modern C++ Item 7&8

テンプレートとnullptr

• 毎回ロックするのはあんまりなので、テンプレートを使って以下のようにしてみると

© 2015 sprout Inc. All Rights Reserved.22

template<typename FuncType,

typename MuxType,

typeName PtrType>

auto lockAndCall(FuncType func,

MutexType& mutex,

PtrType ptr) -> decltype(func(ptr)) // Item3参照

{

MuxGuard g(mutex);

return func(ptr);

}

auto result1 = lockAndCall(f1, f1m, 0); // エラー!

auto result2 = lockAndCall(f2, f2m, NULL); // エラー!

auto result3 = lockAndCall(f3, f3m, nullptr); // 成功

Page 23: Effective Modern C++ Item 7&8

テンプレートとnullptr

• 0やNULLリテラルの代入なら、その場で(ヌルポインタに)変換されて渡される。

• テンプレートに与えた場合はテンプレートとしての型解決が先に動いてしまう。

• std::shared_ptr<Widget>やstd::unique_ptr<Widget>にintを渡そうとしてエラー

• nullptrはstd::nullptr_t型なので問題無し。

© 2015 sprout Inc. All Rights Reserved.23

Page 24: Effective Modern C++ Item 7&8

Things to Remember

• 0やNULLでなくnullptrを使おう

• 整数型とポインタのオーバーロードは止めよう

© 2015 sprout Inc. All Rights Reserved.24