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


生文字リテラル

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 reference と move semantics

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

その場限りの一時的な値をコピーせずに奪う。
右辺値参照演算子&&を関数やコンストラクタの定義時に使用できる。

class RvrTest {
public:
	static void test() {
		RvrTest a = RvrTest();
		puts("-----");
		RvrTest b = RvrTest();
		puts("-----");
		a = b; // コピー
		puts("-----");
		a = RvrTest();
		puts("-----");
		a = std::move(b);
	}

	~RvrTest() {
		puts("destructor");
	}

	RvrTest() {
		puts("default constructor");
	}

	RvrTest(const RvrTest& other) {
		puts("copy constructor");
	}

	const RvrTest& operator=(const RvrTest& other) {
		puts("copy assignment operator");
		return *this;
	}

#if 1
	// ムーブコンストラクタとムーブ代入演算子
	RvrTest(RvrTest&& other) {
		puts("move constructor");
	}

	const RvrTest& operator=(RvrTest&& other) {
		puts("move assignment operator");
		return *this;
	}
#else
	RvrTest(RvrTest&& other) = delete;
	const RvrTest& operator=(RvrTest&& other) = delete;
#endif
};

結果

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++のラムダ関数も変数のキャプチャができるが
JavascriptやC#などとは違いキャプチャの挙動を変数毎に指定できる。

四角括弧にどのようにキャプチャをするか指定する。
Objective-CのACR+Blocksみたいに勝手にメモリ管理はしてくれないので、
キャプチャした変数に保持しているインスタンスが破棄される可能性がある場合は
ディープコピーなりしておく必要がある。

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

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

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

(){ 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不可(コンパイルエラー)
  1. コメントはまだありません。

  1. トラックバックはまだありません。

*


Advertisement