2014年7月26日土曜日

【読書ノート】Effective Modern C++ - Item 4

O'Reilly Early Releaseで購入したEffective Modern C++のドラフト版の読書記録です。
※この記事はあくまで私の備忘録です。(断りなく自分の解釈や考え・感想を入れたり、理由もなく内容を省略したり、間違いや曲解もあると思います。誤りの指摘は歓迎です。)

Item 4では推定された型を表示するテクニックが書かれていました。 コンパイルエラーメッセージを利用したアイデアは単純で面白いです。 実行時は、プラットフォーム依存であっても__PRETTY_FUNCTION__ or __FINCSIG__を使うやり方が楽だと思いました。

Item 4: 推定された型を見る方法を知ろう

デバッグや実験、単純な疑問、それらのために型推定された型を知りたいことがある。

IDEを使う

auto x = theAnser;
auto y = &theAnser;

autoのところにカーソルを持っていけば表示されるというやつ。IDEのいいところ。

コンパイラの出力

コンパイルエラーで型の情報を見る方法。 定義の書かれていないこんなクラスを宣言し、実体化させる。

template<typename TD>
class TD; // TDは"Type Displayer"の略

TD<decltype(x)> xType;
TD<decltype(y)> yType;

するとTDは定義が無いのでコンパイルエラーになり、その時xとyの型がエラーメッセージの中に表示される。 TD<int>、TD<const int>を実体化できないという文脈で表示される。

実行時の出力

まず思いつくのはtypidからstd::type_info::nameを取り出す方法。(しかし後述の通りこの方法は誤り)

std::cout << typeid(x).name() << '\n';
std::cout << typeid(y).name() << '\n';

しかし読みやすいものが返されることは保証されていない。
GNUとClangはxの型をi、yの型をPKiと表示する。
iはint、Pはポインタ、Kはconstを意味する。
Microsoftのコンパイラはxの型をint, yの型をint const *と表示する。

template<typename T>
void f(const T& param)
{
    using std::cout;
    cout << "T = " << typeid(T).name() << '\n';
    cout << "param = " << typeid(param).name() << '\n';
}

const auto vw = createVec(); // std::vector<Widget>を返すファクトリ関数
if (!vw.empty()) {
    f(&vw[0]); // &vw[0]の型はconst Widget *
}

// 出力結果 (gcc)
// T = PK6Widget
// param = PK6Widget
// ※6はクラス名の文字数を示す

// 出力結果 (MSVC)
// T = class Widget const *
// param = class Widget const *
// "Widget const *"は"const Widget *"と同じ

Tは合っているが、paramは本来const Widget * const &のはずである。
これは、std::type_info::nameが型を値渡しされた時のように扱うため。
参照とconstが取り除かれるため、const Widget *になってしまう。

また、この場合、残念なことにIDEも解読しづらい結果を返してくる。

// Tの型
const
std::_Simple_types<std::_Wrap_alloc<std::_Vec_base_types<Widget,
std::allocator<Widget> >::_Alloc>::value_type>::value_type *

// paramの型
const std::_Simple_types<...>::value_type *const &

typedefを追っていけば実際に指している型が分かるが、長い上にparamの方は...と省略されてしまっている。

一方、コンパイラのエラーメッセージは信頼できる。

TD<T> TType;
TD<decltype(param)> paramType;

とすればコンパイル時に正しい型を表示してくれる。

実行時に正しい型情報を表示するため、std::is_constやstd::is_pointer, std::is_lvalue_referenceなどとtypeidを組合せて自作の表示関数を作るのも手だ。
しかし、プラットフォーム依存であることを許せば簡単な方法がある。

template<typename T>
void f(const T& param)
{
    #if defined(__GNUC__)
        std::cout << __PRETTY_FUNCTION__ << '\n'; // GNUとClang用
    #elif defined(_MSC_VER)
        std::cout << __FUNCSIG__ << '\n'; // Microsoft用
    #endif
}
// 出力結果
// GNU
// void f(const T&) [with T = const Widget*]
// Clang
// void f(const Widget *const &)
// Microsoft
// void __cdecl f<const classWidget*>(const class Widget *const &)

Things to Remember

  • 推定された型はIDEのエディタ上か、コンパイルのエラーメッセージか、typeidか、__PRETTY_FUNCTION__や__FUNCSIG__などの言語拡張で見ることができる。
  • しかしそれらの表示は有用でなかったり正確で無かったりするので、C++標準規格の型推定ルールを理解していることは相変わらず重要。

2014年7月24日木曜日

【読書ノート】Effective Modern C++ - Item 3 [2 / 2]

Item 3の続きです。
以下のC++14の関数にまだ改善の余地があるというお話から。

universal referenceでrvalueをサポートする

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i);

この関数にはrvalueは渡せない。(Container&がconst参照でないため)
rvalueを渡すのはマイナーなケースだが、意味のある例は存在する

std::deque<std::string> makeStringDeque();
// dequeの5番目の要素のコピーを得る文
auto s = authAndAccess(makeStringDeque(), 5);

これをサポートするにはuniversal reference(詳細はItem 26)を使用すれば良い。

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i);

なお、Indexは標準ライブラリの例(std::string, std::vector, std::dequeなど)が合理的だと思えるため値渡しにしている。
加えてItem27の勧告に従ってuniversal referenceにstd::forwardを適用する。

template<typename Container, typename Index> // C++14の最終版
decltype(auto)
authAndAccess(Container& c, Index i)
{
    authenticateUser();
    return std::forward<Container>(c)[i];
}

template<typename Container, typename Index> // C++11の最終版
auto
authAndAccess(Container& c, Index i)
-> decltype(std::forward<Container>(c)[i])
{
    authenticateUser();
    return std::forward<Container>(c)[i];
}

decltypeの僅かな特殊なケース

decltypeは、変数名に関してはその変数の型を返すが、変数名以上に複雑なlvalueは全てlvalue referenceとして返す。
つまり、変数名以上に複雑な式のT型はT&型と型推定する。
しかしそのようなケースは少ない。(たとえば関数が返すlvalueはlvalue referenceである。さもなくばrvalue)
変数名以上に複雑なlvalueの例は、(x)だ。

int x = 0;
// decltype(x)はint
// decltype((x))はint&

decltype(auto) f1()
{
    int x = 0;
    ...
    return x; // f1の戻り値はint
}

decltype(auto) f2()
{
    int x = 0;
    ...
    return (x); // f2の戻り値はint&
}

f2の戻り値はローカル変数の参照なので、戻り値を受け取ったら未定義動作となってしまう!

注意点はあるが、通常の場合はdecltype(auto)は直感的な動作をする。
特に変数名に適用する場合は、decltypeという名前通り、変数の宣言された型(declared type)を返す。

Things to Remember

  • decltypeはほぼ常に変数や式の型を修正無しに生成する
  • 変数名より複雑なlvalue(たとえば"(x)")のTに対してdecltypeは常にT&を返す
  • C++14はdecltype(auto)をサポートし、decltypeのルールに従って型推定する

2014年7月23日水曜日

C++で数値を文字列に変換する最も高速な方法はBoost.Spirit.Karma

C++で数値を文字列に変換する方法は、sprintf, iostreamのstd::stringstream, boost::lexical_cast, boost::formatなどが有名ですが、最も実行速度の速い方法は他にあり、Boost.Spirit.Karmaを使用する方法です。
Boost.Spirit.Karmaの初歩的な使用方法は以下のソースコードを参考にしてください。
https://gist.github.com/muumu/d8e399c544076301e94d
また、boost公式のパフォーマンス計測が自分にとって不十分だったので、下記の改変したソースコードでパフォーマンス計測を実行しました。
https://gist.github.com/muumu/3b1801456481b447f3a2
ltoaは自分の環境に無いためコメントアウト、代わりにsnprintfを追加。(snprintfは、sprintfより安全で、同程度か僅かに速い関数です。)

パフォーマンス計測結果

前回、小数のフォーマット出力が最も高速なのがBoost.Spirit.Karmaであることを紹介したので、今回は整数値の計測を示します。
処理内容は、ランダム値を表示するものです。
(実行環境: boost: 1.54.0, gcc: 4.8.2 コンパイルオプション: -O3, -std=c++1y, CPU: Core i3 4130T)

Converting 10000000 randomly generated int values to strings.
snprintf (std::string):                 1.06302 [s]
snprintf (char[]):                      0.834953 [s]
iostreams (std::string):                1.23543 [s]
iostreams (std::stringstream):          0.673501 [s]
Boost.Format (std::string):             4.26696 [s]
boost::lexical_cast (std::string):      0.806607 [s]
Karma int_ (std::string):               0.326372 [s]
Karma int_ (char[]):                    0.131378 [s]
Karma (std::string):                    0.254512 [s] // 直接std::stringに出力するkarma
// ()内は出力形式。
// char配列型で()内がstd::stringなものは、出力結果を改めてstd::string型に代入してます

Karmaはsnprintfの実に5倍前後高速な結果となりました。 Boost.Spirit.Karmaが何故こんなに高速なのか、興味が出たので今後調査したいと思っています。

2014年7月20日日曜日

Boost.Spirit.Karmaのフォーマット出力が異常に早い

Comparing the performance of a sequence of several generatorsを見てBoost.Spirit.Karmaのフォーマット出力が優秀だと知ったのですが、最近のgccとboostだとどうなるかと思って自分のLinux環境でも実行してみました。
(実行環境: boost: 1.54.0, gcc: 4.8.2 コンパイルオプション: -O3, -std=c++1y, CPU: Core i3 4130T)
ソースは上記ページのものにsnprintfも加えたものを実行しました。(最近はセキュルティの観点でsprintfよりsnprintfが推奨されるため)
処理内容は、12345.12345という小数2つから[12345.123 12345.123 ]という文字列(小数点以下3桁、14文字分のスペース)を繰り返し生成するものです。
すると、Spirit.Karmaがsprintfの4倍近く速いことが分かりました。(-O2オプションでもほとんど同じ結果でした。)

sprintf:        1.13721
snprintf:       1.12995
iostreams:      1.39023
format:         1.99459
karma:          0.231521
karma (string): 0.318302
karma (rule):   0.294326

※iostremasはstd::stringstream、formatはboost::formatを指しています。

また、元々のソースでは12345.12345が直に書かれていたので、定数の最適化が行われている可能性を考えて標準入力から数値を与えるようにソースを改変してみましたが、結果はほとんど変わりませんでした。
疑いようもなくSpirit.Karmaが速いです。

input a decimal
12345.12345
input one more decimal
12345.12345
sprintf with args:      1.14024
snprintf with args:     1.12748
iostreams with args:    1.38619
format with args:               2.01176
karma with args:                0.228151
karma (string) with args:       0.314551
karma (rule) with args: 0.293565

なお、数値の桁数が小さい時は差が縮まりました。

input a decimal
1.0
input one more decimal
2.0
sprintf with args:      0.412689
snprintf with args:     0.393523
iostreams with args:    0.640817
format with args:               1.2292
karma with args:                0.263695
karma (string) with args:       0.327404
karma (rule) with args: 0.307239

Boost.Spirit.Karmaはコードが複雑でコンパイル時間も増えるのでカジュアルに使うのには向かないですが、処理速度を求める時には最適なようです。
カジュアルに使う場合は、処理速度は遅いですがsprintf風の記法で型安全でもあるboost::formatが使いやすいですね。
std::stringstreamは、数値を入れる時に余計なバッファを確保するので遅いようです。(boost::formatよりは速いですが。)

ちなみに、int型をstd::string型に変換するのみの場合でもSpirit.Karmaは速いようです。
C++ Convert Int to String Speed
これを見てもうひとつ分かるのは、boost::lexical_castがsprintf以上に速いということです。
カジュアルに数値を文字列に変換するだけなら、boost::lexical_castがベストチョイスになりそうです。
(C++11以降ではstd::to_stringという選択肢もありますが、こちらの処理速度等の詳細は未確認です。)

【読書ノート】Effective Modern C++ - Item 3 [1 / 2]

O'Reilly Early Releaseで購入したEffective Modern C++のドラフト版の読書記録です。
※この記事はあくまで私の備忘録です。(断りなく自分の解釈や考え・感想を入れたり、理由もなく内容を省略したり、間違いや曲解もあると思います。誤りの指摘は歓迎です。)

Item 3はdecltypeについてです。
自分はC++11の関数の戻り値を後ろに書く記法とC++14のdecltype(auto)をちゃんと知らなかったので、学ぶところが多かったです。

Item 3: decltypeを理解しよう

decltypeは名前や式の型を教えてくれる。
典型的の場合は予想通りの動きをしてくれるが、時には予想に反する動きをする。

典型的な場合

const int i = 0; // decltype(i)はconst int
bool f(const Widget& w); // decltype(w)はconst Widget&
    // decltype(f)はbool(const Widget&)

struct Point {
    int x, y; // decltype(Point::x)はint。Point::yも同様。
};

Widget w; // decltype(w)はWidget

if (f(w)) ... // decltype(f(w))はbool

template<typename T>
class vector {
public:
    ...
    T& operator[](std::size_t index);
    ...
};

vector v; // decltype(v)はvector
...
if (v[0] == 0) ... // decltype(v[i])はint&

関数の戻り値の推定

C++11の場合

主なdecltypeの使用場所はtemplate関数で、テンプレートパラメータに依存する型を返したい時だ。

template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i) // 改善の余地あり(後述)
    -> decltype(c[i])
{
    authenticateUser();
    return c[i];
}

これにより、c[i]が何の型を返そうともそれをそのまま関数の戻り値とすることができる。
なお、この関数名の前のautoは型推定とは関係ない。
このautoは単にC++11のtrailing return type syntax(戻り値を後置する記法)が使用されることを示す。
この後置のメリットは、戻り値の型の計算に関数パラメータを使用できること。
(上記例ではcとi。後置を使わないと、cとiは利用できない。)

C++14の場合

C++14では、以下のようにautoだけで戻り値の型推定が行われるようになった。
(この場合はもちろん、autoは型推定を示すキーワードとなる。)

template<typename Container, typename Index> // C++14の記法 + 改善の余地あり
auto authAndAccess(Container& c, Index i)
{
    authenticateUser();
    return c[i]; // 戻り値の型がc[i]から推定される
}

しかしこの場合、型推定のルールは?autoとdecltypeのどちらかのもの?
驚くかもしれないが、答えはテンプレートの型推定のルールになる。
ただ、初期化リスト{}を除けばautoとテンプレートの型推定のルールは同じだ。
しかし今回の場合はこのルールだと問題になる。
operator[]は大抵参照を返すが、テンプレートの型推定では参照を取り除いてしまうからだ。
従って、以下のコードはコンパイルエラーになる。

std::deque d;
...
authAndAccess(d, 5) = 10; // コンパイルエラー

これは、d[5]はint&を返すがテンプレートの型推定によって戻り値の型はintになるため。
すると、rvalueに10を代入しようとしていることになるため、コンパイルエラーとなる。
この問題は、decltype(auto)指定子によってdecltypeの型推定をさせるようにすれば解決する。

template<typename Container, typename Index> // C++14の記法 + 改善の余地あり
decltype(auto)
authAndAccess(Container& c, Index i)
{
    authenticateUser();
    return c[i];
}

変数宣言にdecltype

decltype(auto)は変数宣言にも有効な場面がある。

Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // myWidget1の型はWidget
decltype(auto) myWidget2 = cw; // myWidget2の型はconst Widget&

authAndAccessで改善の余地があるとされていることと、decltypeの型推定の典型的でない場合については、次回の記事で。

2014年7月17日木曜日

【読書ノート】Effective Modern C++ - Item 2

O'Reilly Early Releaseで購入したEffective Modern C++のドラフト版の読書記録です。
※この記事はあくまで私の備忘録です。(断りなく自分の解釈や考え・感想を入れたり、理由もなく内容を省略したり、間違いや曲解もあると思います。誤りの指摘は歓迎です。)

Item 2は、Item 1を理解していればすぐ読み終わる内容でした。
ただ、auto x = {27};でxがinitializer_list型になるのは知らなかったので新鮮でした。

Item 2: autoの型推論を理解しよう

autoの型推論は、1つの例外をのぞいてテンプレートの型推定と同じ。

autoをテンプレート関数のパラメータの型だと思えば同じだと分かる。

auto x = 27; // case 3 (xはポインタでも参照でもない)、xはint
const auto cx = x; // 同上、cxはconst int
const auto& rx = x; // case 1 (rxはuniversalでないreference)、rxはconst int&
auto&& uref1 = x; // xはintでlvalueなので、uref1の型はint&
auto&& uref2 = cx; // cxはconst intでlvalueなので、uref2の型はconst int&
auto&& uref3 = 27; // 27はintでrvalueなので、uref3の型はint&&

const char name[] = "R. N. Briggs"; // nameの型はconst char[13]
auto arr1 = name; // arr1の型はconst char*
auto& arr2 = name; // arr2の型はconst char(&)[13]

void someFunc(int, double); // someFuncは関数で、型はvoid(int, double)
auto func1 = someFunc; // func1の型はvoid (*)(int, double)
auto func2 = someFunc; // func2の型はvoid (&)(int, double)

ここまではItem 1と本質的に同じ。

autoとテンプレートの型推定のただ1つの違い

以下の4つは皆int型変数を27で初期化する文。
(下の2つはC++11で導入されたuniform initialization)

int x1 = 27;
int x2(27);
int x3 = {27};
int x4{27};

しかし、autoを使うと同じでなくなる。

auto x1 = 27; // int型で、値は27
auto x2(27); // 同上
auto x3 = {27}; // std::initializer_list<int>型で、値は{27}
auto x4{27}; // 同上

以下の文はstd::initializer_list<T>のTを推定できないためコンパイルエラーになる。

auto x5 = {1, 2, 3.0};

まずx5は{}の存在によりstd::initializer_listと推定される。
次に、std::initializer_listはテンプレートなのでstd::initializer_list<T>のTが決まらなければならない。
しかし、{}内の数値型が同じではないのでTが推定できずコンパイルエラーとなる。
この{}による初期化のみがテンプレートとautoの型推定の違い。

なぜこのような仕様になっているか、説得力のあるな説明はない。ルールである。
この落とし穴のため、{}による初期化はそうしなければならない時のみに使用する開発者もいる。
({}を使わなければならない場面はItem 7に説明がある。)

C++11の範囲では話は以上だが、C++14では続きがある。
C++14ではラムダ式でautoが使える。
そしてこのautoはテンプレートの型推定を行う。({}をstd::initializer_listとは推定しない。)
従って、下記のコードは{ 1, 2, 3 }の型を推定できないのでコンパイルに失敗する。

auto createInitList()
{ return { 1, 2, 3 }; // コンパイルエラー } std::vector v; ... auto resetV = [&v](const auto& newValue) { v = newValue; } ... resetV( { 1, 2, 3 } ); // コンパイルエラー

Things to Remember

  • autoの型推定は通常テンプレートの型推定と同じ
  • 唯一の例外はautoと{}による初期化を使用した変数宣言で、そこでautoはstd::initializer_list型を推定する
  • テンプレートの型推定は{}による初期化リストを与えられると失敗する

【読書ノート】Effective Modern C++ - Item 1 [2 / 2]

今回はItem 1の残り、配列型と関数ポインタ型の型推定です。
配列型は、自分でも関数に渡すとポインタ型に退化してしまって要素数が取れなくなるのを不便に感じたことがあるのですが、テンプレートで参照渡しにするとその問題が解決するのを不思議に思っていましたが、今回の説明を読んでそれが仕様通りの現象であることを理解できました。

配列引数

配列型とポインタ型は同一視されがちだが、実は違う
配列のポインタ型は、配列型が配列の先頭要素へのポインタに壊変したもの

const char name[] = "J. P. Briggs"; // nameの型はconst char[13]
const char * ptrToName = name; 配列型はポインタ型に壊変した

しかし、パラメータ型は強制的にポインタ型に壊変してしまう
void myFunc(int param[]);
これは以下の宣言と同じになってしまう
void myFunc(int* param);

これはC++がC言語を元としているため生じたもの。
下記のように値渡しのテンプレート関数では、配列を渡すとポインタ型になる。
template <typename T>
void f(T param); // 値渡しのテンプレート関数

f(name); // nameはconst char[13]型だが、const char *型と型推定される
しかし、参照渡しのテンプレート関数では、配列の参照型にできる

template <typename T>
void f(T& param); // paramはconst char (&)[13]型となる

f(name); // nameはconst char [13]型
これを使うと、コンパイル時定数として配列の要素数を返すconstexpr関数を定義できる
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) {
    return N;
}

int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 }; 

int mappedVals[arraySize(keyVals)]; 

関数引数

配列型と同じように、関数型も関数ポインタ型に壊変する

void someFunc(int, double); // 型はvoid(int, double)

template<typename T>
void f1(T param); // 値渡し

template<typename T>
void f2(T& param); // 参照渡し

f1(someFunc); // 値渡しは関数ポインタ型void (*)(int, double)に。

f2(someFunc); // 参照渡しは関数の参照型void (&)(int, double)に。

Things to Remember

  • ポインタ型とUniversa Referenceでない型のパラメータでは、引数が参照型かどうかは無視される
  • パラメータがUniversal Referenceの場合は、lvalueはlvalue referenceに、rvalueはrvalue referenceになる
  • パラメータが値渡しの場合、引数が参照型かどうか、constかどうかは無視される
  • 配列型と関数型の引数は、パラメータが参照型でない限り、ポインタ型に壊変する

2014年7月16日水曜日

【読書ノート】Effective Modern C++ - Item 1 [1 / 2]

O'Reilly Early Releaseで購入したEffective Modern C++のドラフト版の読書記録です。

※この記事はあくまで私の備忘録であり、本の内容のまとめではないことに注意してください!(断りなく自分の解釈や考え・感想を入れたり、理由もなく内容を省略したり、間違いや曲解もあると思います。誤りの指摘は歓迎です。)

Chapter 1では型推定、Item 1ではテンプレートの型推定が出てきました。直感的に使えますが、ちゃんとしたルールは知らなかったので勉強になりました。また、Universal Referenceという用語は初耳で、挙動も知らなかったのでこちらも勉強に。

Chapter 1 型推定

C++98から関数テンプレートでは型推定が行われていた。
C++11ではautoとdecltypeが加わり、C++14ではその2つを使用できる文脈が増えた。
型推定は、型を変更する時に1箇所変えるだけで他の部分にも伝播するため、手で複数箇所を変更する手間から解放してくれる。
この章は型推定の基本的な情報を提供する。

Item1: テンプレートの型推定を理解しよう

テンプレートの型推定は、詳細な仕組みを知らなくても直感的に使える優れたシステムの一例。
autoで宣言された変数の型推定は、テンプレートの型推定と本質的には同じ。
しかし、仕組みを理解していないと面食らうこともあるだろう。
template <typename T>
void f(const T& param); // パラメータの型はconst T&

int x = 0;
f(x); // 引数型はint
ここではTはint型と推定されているが、パラメータの型はconst int&と推定されている
※復習: paramはパラメータ、xは引数(パラメータと引数の区別)

Case 1: パラメータの型がポインタ/参照だが、Universal Referenceではない場合

  • 引数の型が参照の場合、参照を無視する 
  • 引数の型をパラメータの型とパターンマッチすることでTを決定する
template<typename T>
void f(T& param);

int x = 27;
const int cx = x;
const int& rx = x;

f(x); // Tはint、パラメータの型はint&
f(cx); // Tはconst int、パラメータの型はconst int&
f(rx); // Tはconst int、パラメータの型はconst int&
注目すべき点
  • 引数のconst性は保たれるので安全に使える
  • 参照を渡しても、(C++では禁止されている)参照の参照にはならない(自動的に余計な参照が除かれる)
パラメータがconst参照になった場合
template<typename T>
void f(const T& param);

f(x); // Tはint、パラメータの型はconst int&
f(cx); // Tはint、パラメータの型はconst int&
f(rx); // Tはint、パラメータの型はconst int&
自然なものとして納得できる
パラメータ型がポインタ型の場合も本質的に同様。
template<typename T>
void f(T* param);

int x = 27;
const int *px = &x;

f(&x); // Tはint、パラメータの型はint*
f(px); // Tはconst int、パラメータの型はconst int*

Case2: パラメータの型がUniversal Referenceの場合

Universal Referenceというのは初耳だったのでググったところ一番上に著者自身の説明が出てきました。
http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
Item26でも説明があるようです。
rvalue referenceと同じ見た目ですが、とりあえず型推定の時のみ登場するのでT&&とかauto&&がUniversal referenceだと当面の間は思っておけばいいっぽいです。
それで、その具体的な挙動については以下の通り、lvalueを引数として受けたっ時に挙動が直感的でないので要注意。

ルール
  • 引数がlvalueの時、Tとパラメータの型はlvalue referenceとなる
  • 引数がrvalueの時、通常の型推定のルールが適用される
template<typename T>
void f(T&& param);

int x = 27;
const int cx = x;
const int& rx = x;

f(x); // xはlvalueなので、Tはint&、パラメータの型もint&
f(cx); // cxはlvalueなので、Tはconst int&、パラメータの型もconst int&
f(rx); // rxはlvalueなので、Tはconst int&、パラメータの型もconst int&
f(27); // 27はrvalueなので、Tはint、パラメータの型はint&&

Case 3: パラメータ型がポインタでも参照でも無い場合

つまり、値渡しになる場合。

template<typename T>

void f(T param);

paramは引数のコピー、完全に新しいオブジェクトとなる。

ルール
  • 引数の型が参照の時、参照部分は無視される
  • 引数の型(参照の場合は参照を除去されたのち)constもvolatileも無視される
int x = 27;
const int cx = x;
const int& rx = x;

f(x); // Tもパラメータもint
f(cx); // Tもパラメータもint
f(rx); // Tもパラメータもint
パラメータは引数とは独立な新しいオブジェクトなので、const性は無視しても問題ないためこうなっている。
ただし、constが無視されるのは値渡しのパラメータ(今回のケース)の場合のみであることは重要。
template<typename T>
void f(T param);

const char* const ptr = "Fun with pointers";

f(ptr);

ptrはconstオブジェクトに対するconstポインタなので、アドレスも変更できないし値をnullにすることもできない。
これをfに渡した時、ポインタが値渡しとなるためTはconst char*となる。
つまり、ポインタに対するconstは失われ、ポインタの先のオブジェクトのconstは残る。

【読書ノート】Effective Modern C++ - Item 1 [2 / 2]

2014年7月14日月曜日

【読書ノート】Effective Modern C++ - Introduction

O'Reilly Early Releaseで購入したEffective Modern C++のドラフト版の読書記録です。

※この記事はあくまで私の備忘録であり、本の内容のまとめではないことに注意してください!(断りなく自分の解釈や考え・感想を入れたり、理由もなく内容を省略したり、間違いや曲解もあると思います。誤りの指摘は歓迎です。)

Introductionでは、C++11で新たに登場した概念、特に(私のような)初心者がつまずきやすいとこを中心に用語説明が書かれていました。

C++11での大きな変更

  • auto
  • range-based for文
  • lambda式
  • rvalue reference
  • 新しい非同期処理
さらに他にも
  • 0がnullptrに
  • typedefがalias declarationsに
  • Enums should now be scoped
  • スマートポインタが生ポインタより好ましいものに
  • 通常、オブジェクトのmoveがcopyよりもいいように

とにかくC++11には学ぶべきものがたくさんある。さらにC++14まで取り入れたらより多くなる。
そしてそれ以上に重要なことは、新しい機能を効果的に使うために学ぶべきことがたくさんあることだ。
C++11、C++14の機能・特徴については既に情報が溢れている。
この本の目的は、それらの機能を正しく、効率的に、保守性・移植性を保つように使いこなすことだ。
注意点として、この本の各itemはガイドラインであってルールではない。
ガイドラインに例外はつきものなので、最も重要なのはアドバイスの背後にある論理。

用語と慣例

ISO標準規格のバージョン: C++98, C++03, C++11, C++14
C++03はC++98のマイナーバージョンアップなので、この本では同一視する
C++14はC++11の上位互換なので、C++11と言ったらC++14の内容も指すことにする
C++14と書いた場合はC++14のみを指す
単にC++と書いた場合は全てのバージョンで成立することを指す

lvalueとrvalue

lvalueとrvalueを見分ける経験則はアドレスを取れるかどうか
Widget(Widget&& rhs); // rhsはrvalue reference型を持っているが自身でアドレスを持っているのでlvalue

ムーブセマンティクスの登場で変わった初期化コスト

void someFunc(Widget w);
この関数宣言を見ただけでは、Widget wの初期化にどの程度計算コストがあるかは分からない
lvalueが渡されたらコピーコンストラクタによって初期化されるし、rvalueならムーブコンストラクタで初期化される

パラメータと引数の区別

void someFunc(Widget w); // wは関数のパラメータ (parameter)
Widget wid;
someFunc(wid); // widは関数の引数 (argument)
someFunc(std::move(wid)); // std::move(wid)は関数の引数
パラメータはlvalue、引数はlvalueかもしれないしrvalueかもしれない

例外安全(exception-safety)の概念

よく設計された関数はexception-safeである。
basic guaranteeでは、例外が発生してもデータ破壊やリソースリークが生じないことを保証する
strong guaranteeでは、例外が発生しても(その関数が)呼び出される前と状態が変わらないことを保証する

callable entity

callable entityとは、非メンバ関数呼び出しの形式で呼び出すことのできるあらゆるものを指す。(関数、関数ポインタ、関数オブジェクトなど。)
ラムダ式で生成された関数オブジェクトはクロージャーとして知られているが、この本では区別せずラムダ式と呼ぶ。

deprecate

基本的にC++は後方互換性を保っているが、C++11でdeprecateされたものもある
deprecateは今後の規格で取り除かれる可能性があることを示す
たとえばstd::auto_ptrはstd::unique_ptrの方が良いのでdeprecatedとなった

ctor: constructor(コンストラクタ)の略
dtor: destructor(デストラクタ)の略

【読書ノート】Effective Modern C++ - Item 1 [1 / 2]

2014年7月12日土曜日

Effective Modern C++ のドラフトバージョンが販売開始

Scott Meyers氏によるEffective Modern C++のドラフトバージョンが本日公開されました。
http://scottmeyers.blogspot.jp/2014/07/draft-version-of-effective-modern-c-now.html

pdf版がO'Reilly の Early Releaseにて$42.99で販売されています。
http://shop.oreilly.com/product/0636920033707.do
こちらはクレジットカードやPayPalで購入でき、購入後は正式版が公開された場合も無料で再ダウンロードすることが可能です。
私も早速購入しました。
Safari Books Onlineからも購入できるようです。

このEffective Modern C++より前から出ているEffective C++は、日本で翻訳本も販売されている有名なC++の参考書です。
より良いデザインで、問題を抑えて効率的に動くC++の書き方について、実際にC++コードを書く初心者向けに解説された良本でした。
しかしC++はC++11やC++14など最近の規格で新たな文法・機能が追加されており、だいぶ様変わりしてきています。
今回のEffective Modern C++ではそこをカバーしてくれると期待しています。

以下、Scott Meyers氏のブログより注意点。

  • フルーカラーであるのが特徴だが、最終版までに修正が入るので今回のバージョンの見た目が最終版のものとは思わないで欲しい
  • このバージョンは6週間前に書かれた状態のもので、既にレビュアーから全体の70%についてコメントを受けており、問題がある
  • 問題には、誤字や文法ミスといった細かいものもあれば、どう述べたら良いか慎重に考えるべき重要な技術的欠陥もある
  • 自分は普段できるだけベストな状態にしてから出版するようにしているので、今回のドラフトバージョンを公開するのは少々緊張している
  • 読者の方で、改善すべき点を見つけた方は、気軽にO'ReillyかSafari onlineのエラーレポートシステムかEメールで知らせて欲しい
  • 以上、抜粋です。

    O'Reillyの電子書籍はpdf形式なので、好きな端末で読めるのが嬉しい点です。早速読んでいこうと思います。

    追記: 読み始めました。
    【読書ノート】Effective Modern C++ - Introduction