2011年12月26日月曜日

型推論

型推論とは

簡単に言えば、関数の戻り値型などが自明である場合、それを受ける変数の型などをコンパイラに判断させる作業のことです。

型推論をコンパイラが行うことで、ソースコードが簡潔になり可読性が良くなります。

C++でコンパイラに型推論を行わせる方法は、autoキーワードを使用する方法と、decltypeキーワードを使用する方法の二種類あります。

autoによる型推論

関数の戻り値を受け取る変数の型を指定するとき、関数の戻り値の型はコンパイラにとっては自明であるため、autoキーワードを変数の型として指定することができます。

void func() {
  std:vector<int> values {1, 2, 3, 4};
  auto sum = 0;
  for (auto i = values.begin(); i != values.end(); ++i) {
    sum += *i;
  }
  std::cout << "sum = " << sum << std::endl;
}

autoを使用しない場合は、次のようにかなり冗長になります。

void func() {
  std:vector<int> values {1, 2, 3, 4};
  int sum = 0;
  for (std::vector<int>::const_iterator i = values.begin(); i != values.end(); ++i) {
    sum += *i;
  }
  std::cout << "Sum = " << sum << std::endl;
}

decltypeによる型推論

変数を定義する時点でその変数の型を明確に定義できない場合、decltypeキーワードを使用してその問題を解決することができます。

int add(int a, int b) {
  return a + b;
}
long add(long a, long b) {
  return a + b;
}
template<typename T>
void func(T v1, T v2) {
  decltype(add(v1, v2)) result;
  result = add(v1, v2);
  std::cout << "Result = " << result << std::endl;
}

ただし、上記の場合はautoキーワードを使ったほうが簡潔になります。

int add(int a, int b) {
  return a + b;
}
long add(long a, long b) {
  return a + b;
}
template<typename T>
void func(T v1, T v2) {
  auto result = add(v1, v2);
  std::cout << "Result = " << result << std::endl;
}

autoとdecltypeをどのように使い分けるか

変数の型を指定する場合は、autoキーワードを指定すればよいかと思います。decltypeを使用するケースとしては、関数の戻り値の型を指定する場合になります。

template<typename T1, typename T2>
auto add(T1 v1, T2 v2) -> decltype(v1 + v2) {
  return v1 + v2;
}

decltypeの読み方

"でくるたいぷ"と読むらしい。

2011年12月22日木曜日

初期化リスト

オブジェクトの初期化方法いろいろ

配列を初期化する場合、次のような初期化リストを使用します。

int[] array = {1, 2, 3, 4, 5};

クラスや構造体を初期化する場合もこの書式を使うことができます。

ClassA a = {1, 2, 3, 4, 5};
StructB b = {1, 2, 3, 4, 5};

このように初期化する場合は、対象のクラスや構造体がPODであるか、次のようにコンストラクタを定義する必要があります。

class ClassA {
public:
  ClassA(std::initializer_list<int> list);
};
struct StructB {
  int a, b, c, d, e;
};

このように、std::initialize_list<T>をコンストラクタの引数に指定することで、初期化リストを使用してオブジェクトを初期化することができます。

関数の引数に初期化リストを使用する

std::initializer_list<T>を関数の引数に指定することができます

int sum(std::initializer_list<int> list) {
  int result = 0;
  for (auto i = list.begin(); i != list.end(); ++i) {
    result += *i;
  }
  return result;
}
int main() {
  int result = sum({1, 2, 3, 4, 5});
}

どんなときに使うか

任意の数の引数を受け取る方法として、可変長引数テンプレートやCの可変長引数があります。それらと比べて引数リストを使用する利点として、「型が指定できる」「配列と同じ書式で初期化できる」と言った店が挙げられます。

とくに、「配列と同じ書式で初期化できる」というのは、可読性が良くなるので積極的に利用したいところです。が、見当違いな使い方をすると、逆に解りづらくなるので使い方には注意が必要です。

2011年12月19日月曜日

外部テンプレート

templateにexternを指定する

ある翻訳単位で完全に引数が特定されたテンプレートが見つかると、コンパイラはそのテンプレートを実体化します。このテンプレートの実体化によりコンパイル時間を増加させます。同じ引数を指定したテンプレートが複数の翻訳単位で実体化されるときは、非常に時間を取られます。

これを解消するために、特定の翻訳単位でテンプレートの実体化をしないように指定することができます。次のサンプルのようにtemplateにexternを指定することで、コンパイル時にtemplateを実体化しません。

extern template class std::vector<ClassA>;

問題点

ただし、当然コンパイル・リンクされたバイナリ内に一つ以上の実態が存在している必要があります。その実態をどこで定義するかをコーディング規約などで取り決める必要があります。

本当は・・・

このぐらいの最適化、コンパイラやリンカで自動的に行なってくれればいいのですが、作りの問題でできないのでしょうか。

TODO

テンプレートの実態をどこで定義するかをどのように取り決めればいいのだろうか?

2011年12月18日日曜日

Plain Old Data

POD(Plain Old Data)とは

Cの構造体のように、連続したバイトデータで表現できる型のことをPODと呼びます。

PODには、intやdoubleなどデフォルト提供されている型と、classやstructで定義するオリジナル型があります。

POD Classの条件

C++11では、PODの条件として"trivialでありstandard-layoutであり、すべての非staticメンバがPOD型である"としています。

trivialであるclassやstructとは、以下の条件を満たしている型になります。

  • コンパイラが定義したデフォルトコンストラクタを持っていること
  • コンパイラが定義したコピーコンストラクタを持っていること
  • コンパイラが定義した代入演算子を持っていること
  • コンパイラが定義した非virtualなデストラクタを持っていること

standard-layoutなclassやstructとは、以下の条件を満たしている型になります。

  • すべての非staticメンバがstandard-layoutな型であること
  • すべての非staticデータメンバが同じアクセス制御であること
  • 仮想関数を持たないこと
  • 仮想基底classを持たないこと
  • すべての基底クラスがstandard-layoutな型であること
  • はじめに定義した非staticなデータメンバの型と同じ型の基本classを持たないこと
  • 継承していて、派生classが非staticなメンバーを持っている場合、基本classが非staticなメンバーを持っていないこと
  • 継承していて、基本classの一つが非staticなメンバーを持っている場合、派生classおよびその他の基本classが非staticなメンバーを持っていないこと

PODを使う理由

PODを定義することで、Cとデータの受け渡しが可能になります。

また、memcpyなどの関数などにより効率的なデータ処理を行えます。

サンプル

単純なPOD class

class Point {
  unsigned int _x;
  unsigned int _y;
public:
  unsigned int x() { return _x; }
  unsigned int y() { return _y; }
};

単純なPOD struct

struct Point {
  unsigned int x;
  unsigned int y;
};

継承する場合 その1

class Base {
public:
  int sum(int a, int b) { return a + b; }
};
class POD : public Base {
  // Baseクラスが非staticなデータメンバを持っていないので、
  // このクラスでは非staticなデータメンバを持つことができる。
  int _a;
  int _b;
public:
  int a() { return _a; }
  int b() { return _b; }
  int sum() { return Base::sum(_a, _b); }
};

継承する場合 その2

class Base {
  int _a;
  int _b;
public:
  int a() { return _a; }
  int b() { return _b; }
};
class POD : public Base {
  // Baseクラスが非staticなデータメンバを持っているので、
  // このクラスでは非staticなデータメンバを持つことはできない。
public:
  int sum() { return a() + b(); }
};

TODO

  • 定義した型がPODであるかを判定する方法
  • PODの条件を満たしていないサンプルを追加