[C++11]メモ - 生文字リテラル, strongly typed enum, static_assert, nullptr, rvalue reference, auto, decltype, lambda, default, delete, override, final

Microsoftさんはいつになったら constexpr を実装してくれるのでしょうか…? 実装状況はコンパイラの実装状況 - cpprefjp C++日本語リファレンス

生文字リテラル

R"文字列" と書くと生文字リテラルになる。

range-based for-loop (いわゆる foreach)

std::vector<int> tests(3);
for (int& test : tests) {
  std::cout << test << std::endl;
}

end(), begin() でイテテータを取得できるものであれば利用可能

strongly typed enum

従来の Enum と違って、

型を指定できて型安全で足したり引いたりの演算が出来ないもの。

あとクラス定数っぽくなる。というか実態としてはクラス定数なのかな?

enum class MyEnum : long {
  Hoge = 100L,
  Piyo
};

void test() {
  MyEnum a = MyEnum::Hoge;
  MyEnum b = MyEnum::Piyo;
  MyEnum c = (MyEnum)((long)a * (long)b);
  printf("%ld", c); // 10100

  a == 100L; // コンパイルエラー
  a + b; // コンパイルエラー
  a - b; // コンパイルエラー
  a * b; // コンパイルエラー
  a / b; // コンパイルエラー
}

static_assert

コンパイル時に行える assert

constexpr int getConstantNumber() {
  return 100;
}

int getNumber() {
  return 100;
}

void test() {
  // コンパイル時に評価されてコンパイルエラー
  static_assert(false, "hoge");

  // これはコンパイルに通る
  static_assert(getConstantNumber() == 100, "piyo");

  // assert失敗でコンパイルエラー
  static_assert(getConstantNumber() != 100, "2000");

  // getNumberはコンパイル時に評価できないのでコンパイルエラー
  static_assert(getNumber() == 100, "message");
}

nullptr

NULL は 数値型の 0 でありポインタ型ではない。

nullptr は数値型ではなくヌルポインタである。

static void func1(int integer) {
  puts("int");
}

static void func2(int *pInteger) {
  puts("int pointer");
}

static void test2() {
  func1(NULL); // int
  func2(nullptr); // int pointer
}

右辺値参照 rvalue referencemove semantics

rvalue とか lvalue とか初耳な低レベルな俺には難解だった…

その場限りの一時的な値をコピーせずに奪う。

右辺値参照演算子&&を関数やコンストラクタの定義時に使用できる。

参考

結果

default constructor
-----
default constructor
-----
copy assignment operator
-----
default constructor
move assignment operator
destructor
-----
move assignment operator
destructor
destructor

std::move

rvalue に変換する

std::string str = "hoge";
std::string copy = str;
std::string move = std::move(str);
puts(move.c_str()); // hoge
puts(copy.c_str()); // hoge

// std::move で str は破壊されているため
// 以下のようなコードを書くべきではない
puts(str.c_str());

auto キーワード(型推論)

※記憶クラス指定子としての auto は消滅(後方互換性なし)

// auto ( * はつけてもつけなくてもコンパイル通る)
auto *pString = new std::string("abcde", 5);
auto pString2 = pString; // * 無しでもポインタ型になる
auto &stringRef = *pString; // 参照型は & をつける必要あり

if (pString) {
  puts(pString->c_str()); // abcde
  pString->append("ABCDE");

  puts(stringRef.c_str()); // abcdeABCDE
  delete pString;
  pString = NULL;

  pString = new std::list<void *>(); // コンパイルエラー
}

// 最初に値型で定義した場合は auto* には代入不可
auto a = std::string("hogehoge");
auto *pA = a; // コンパイルエラー

decltype

変数や関数の型を取り出して、それを型として利用できる。

関数に対して利用できたりと便利。

// decltypeで型を宣言
{
  int num1 = 100;
  decltype(num1) num2 = 200;
  decltype((num2)) num2Ref = num2; // (())で参照になる
  num2Ref = 300;

  printf("%d\n", num2); // 300
}

// 参照型にdecltypeを使うと参照型になる
{
  int num1 = 400;
  int &num1Ref = num1;
  decltype(num1Ref) num2Ref = num1Ref;
  num2Ref = 500;

  printf("%d\n", num1); // 500
}

// ポインタにdecltypeを使うとポインタ型になる
{
  int num1 = 0;
  int *pNum1 = &num1;
  decltype(pNum1) pNum2 = pNum1;
  decltype((*pNum2)) num2Ref = *pNum2;
  num2Ref = 500;
  printf("%d\n", num1); // 500
}

// const付きの変数にdecltypeを使うとconst付いたまま
{
  const int num1 = 600;
  decltype(num1) num2 = 700;
  decltype((num1)) num1Ref = num1;
#if 0
  num2 = 800; // コンパイルエラー
  num1Ref = 900; // コンパイルエラー
#endif
}

// autoに対しても利用できる
{
  const auto &str1 = std::string("STR1");
  decltype(str1) str2 = "STR2";

  puts(str1.c_str()); // STR1
  puts(str2.c_str()); // STR2
}
static std::string func(const char *cstr) { return std::string(cstr); }

class Test
{
public:
  static void test()
  {
    // 関数の戻り値型にdecltype
    decltype(func("何でもいいので引数入れる")) string = "std::string";
    puts(string.c_str()); // std::string

    // 関数ポインタにdecltype
    decltype(&func) pFunc = &func;
    puts(pFunc("FUNC").c_str()); // FUNC

    // lambdaにdecltype : いったんautoに入れないと無理
    auto lambda = [](const char *cstr){ return std::string(cstr); };
    decltype(lambda) *pLambda = &lambda;
    puts((*pLambda)("LAMBDA").c_str()); // LAMBDA
  }
};

本の虫: decltypeの二重括弧が参照になる理由

lambda (ラムダ関数)

ラムダ式と読んでも問題ないのだろうか。

// 何もしないラムダ関数の実行
[](){}(); // 後ろの()が関数実行のカッコ

// 文字を出力 : 引数が無い場合は () は省略可能
auto printTest = []{ puts("test"); };
printTest(); // test

// 返り値の型は推論してくれるが指定も出来る
auto func = [](const char *text)->std::string { return text; };
func("HOGE"); // HOGE

ラムダ関数の型は std::function

変数で受け取る場合は auto を使えばいいんだけど、

引数や戻り値にラムダ式を使用したい場合は auto を指定できずに困ってしまう。

なのでラムダ式の型である std::function 型を指定するかテンプレートメソッドを使う。

class Test {
public:
  static void test1() {
    std::function<std::string(char)> func = [](char c) -> std::string {
      std::string ret;
      ret += c;
      return ret;
    };

    test2(func); // a
    test3(func, 'b'); // b
  }

  static void test2(std::function<std::string(char)> func) {
    puts(func('a').c_str());
  }

  template<class T> static void test3(T func, char c) {
    puts(func(c).c_str());
  }
};

キャプチャ

C++ のラムダ関数も変数のキャプチャができるが

JavascriptC# などとは違いキャプチャの挙動を変数毎に指定できる。

四角括弧にどのようにキャプチャをするか指定する。

Objective-CACR+Blocks のように勝手にメモリ管理はしてくれないので、

キャプチャした変数に保持しているインスタンスが破棄される可能性がある場合は

ディープコピーなりしておく必要がある。

//
// コピー [var] [=]
//
const char *a = "a";
const char *b = "b";

[a](){ puts(a); }(); // [a] で変数aをコピーしてキャプチャ
[=](){ puts(b); }(); // [=] で外部変数全てをコピーしてキャプチャ
[a](){ puts(b); }(); // コンパイルエラー : bはキャプチャしていないため利用不可
[b](){ b = "new b"; }(); // コンパイルエラー : コピーでキャプチャした変数はconst

[b]() mutable -> void { b = "new b"; }(); // mutableでキャプチャした変数のconstを外す
// -> void は省略可能

[b](){ puts(b); }(); // b : 上の式はコピーなので大本のbには影響しない
//
// 参照 [&var] [&]
//
const char *a = "a";
const char *b = "b";

[&a](){ a = "new a"; }();
[a](){ puts(a); }(); // new a
[&](){ puts(b); }(); // b
//
// 複合
//
const char *a = "a";
const char *b = "b";

[=, &b](){ // [&b, =] はダメ。最初に全体を指定する。
  b = "new b";
  a = "new a"; // コンパイルエラー
}();
//
// this のキャプチャ
//
class Test {
 private:
  const char *a = "a";

 public:
  void test() {
    const char *a = "local a";

    [&](){
      this->a = "a2";
      a = "local a2";
    }();
    puts(this->a); // a2
    puts(a); // local a2

    [=, &a](){
      this->a = "a3";
      a = "local a3";
    }();
    puts(this->a); // a3
    puts(a); // local a3
  }
};

default, delete

default を指定すると明示的にデフォルトのコンストラクタなどを自動生成する。

らしいんだけどあんまり使いどころというか使う意味が分からない…

class Test
{
  // デフォルトコンストラクタが定義される
  Test() = default;
  // デフォルトデストラクタが定義される
  virtual ~Test() = default;

  static void test() {
    delete (new Test);
  }
};

delete を指定すると暗黙的に定義されるメンバなどが生成されなくなる。

class Test
{
  Test() = delete;
  Test(int dummy) {};
  Test& operator=(const Test &) = delete;
  Test& operator=(const Test &&) = delete;
  ~Test() = delete;

  static void test() {
    Test *p;
    p = new Test(); // コンパイルエラー
    p = new Test(123);
    new Test(*p); // コンパイルエラー
    delete p; // コンパイルエラー
  }
};

override, final

override はオーバーライドするメンバ関数に対して利用する。もしもオーバーライドしようとした親クラスのメンバ関数が仮想関数でない場合はエラーとなる。つまりオーバーライドのミス防止ができる。あとオーバーライドしていることが明示的になる。

final は派生や継承をできないようにする。クラスに指定した場合はそのクラスの派生クラスは作れない。仮想関数のオーバーライド時に指定した場合は、以降の派生クラスでオーバーライドできなくなる。

class A { void f() {}; };
class B : A { void f() override {}; }; // 非virtualにoverride付けるとコンパイルエラー

class N final {};
class S : N {}; // Aは派生不可(コンパイルエラー)

class X { virtual void f() {}; };
class Y : X { void f () final override {}; };
class Z : Y { void f () override {}; }; // fはoverride不可(コンパイルエラー)
Share
関連記事