Open6
C++学習

インスタンスの構築順序とメンバ変数初期化のタイミングを知るためのコード
結論:構築時、より親のクラスが先に呼ばれる。破壊時、より子のクラスが先に呼ばれる。各クラスを構築するときに一緒に宣言されたメンバ変数が構築される。<=親クラスで一度に構築されるわけではない。
#include <iostream>
using namespace std;
class A {
public:
std::string a = "A";
A() { std::cout << "+A: " << a << std::endl; }
~A() { std::cout << "-A" << std::endl; }
};
class B : public A {
public:
std::string b = "B";
B() { std::cout << "+B: " << b << std::endl; }
~B() { std::cout << "-B" << std::endl; }
};
class C : public B {
public:
A a; // メンバとしての A(基底クラス A とは別)
std::string c = "C";
C() { std::cout << "+C: " << c << std::endl; }
~C() { std::cout << "-C" << std::endl; }
};
int main(void){
C obj;
}

コンストラクタ中に起こった例外はリークに注意する
複数回newしていると、メモリが中に残ることがある。
以下のコードのようにtry-catchで囲んでリークを避ける必要がある。
また、コンストラクタが成功しないとデストラクタが呼ばれないため注意する。親クラスのコンストラクタが成功していれば、親クラスのデストラクタは呼ばれる。当たり前かもしれないが。
#include <iostream>
using namespace std;
class A {
public:
A() {
cout << "+A" << endl;
}
~A() {
cout << "-A" << endl;
}
};
class B : public A {
public:
B() {
int* can_p = new int[1];
cout << "+B (about to throw)" << endl;
int* p;
try {
// int i = -1;
// p = new int[i]; // 明らかなエラー(未定義動作だが実装依存で bad_alloc などを投げる)
throw std::bad_alloc(); // 上記挙動を再現したコード。Sanitizerやvalgrindだと途中でコードが止まるため。
}
catch(const std::exception& e) {
delete [] can_p; // ここがなかったらリーク
throw;
}
std::cout << p << std::endl;
cout << "+B (should not reach here)" << endl;
}
~B() {
cout << "-B" << endl;
}
};
class C : public B {
public:
C() {
cout << "+C" << endl;
}
~C() {
cout << "-C" << endl;
}
};
int main() {
try {
C obj;
} catch (const std::bad_alloc& e) {
cout << "[caught bad_alloc]: " << e.what() << endl;
} catch (...) {
cout << "[caught unknown exception]" << endl;
}
}

try-catch rethrow
あまり例を見ないがtry-catchは、catchを複数回書ける。
また、処理できなかったものはthrow;とすることで同じエラーを投げれる。
こうすることで、型が変わらない。
catch(...)とすることですべてのエラーを受け取れる。(可能なら使わない)
#include <iostream>
using namespace std;
int main() {
try
{
try
{
try
{
throw std::bad_alloc();
}
catch (const std::runtime_error& e) {}
catch (const int& e) {}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
throw;
throw e; // <= ここは通らないが良くない。この例だとstd::bad_allocからstd::exceptionに型が変わってしまう。意図的ならOK
}
}
catch(const std::bad_alloc& e) {
std::cerr << e.what() << std::endl;
throw ;
}
}
catch(...) {
std::cerr << "なんかのエラー" << std::endl;
}
return (0);
}

newの例外処理
newは例外エラーstd::bad_allocを投げる。
そのためエラー処理が必要。
特に同じスコープで複数回new or メモリ確保しているコードがある場合メモリリークに注意する。
確保できた場合は、deleteを使ってきちんと解放しないとメモリリークが発生する。
スマートポインタを使用するとある程度回避可能。
new int [(unsigned inti) -1] // -1 をunsignedに変換して最大の値が設定される。確保できないはずなので例外が送信される。

privateな属性には、google スタイルなら後ろに_
をつけると良いという話を聞いた。
確かに。
また、thisがなくても問題ないことを知った。昔、using namespaceを使っていたときの名残で、自クラスのメソッド or 属性なのかわからないときがありそれを避けるために、thisをつけていたが、スコープ演算子::
を使用するため明確だった。はんしぇ。

コーディングルールに関して