私とc++ in 例外安全day
TRANSCRIPT
私とC++In 例外安全Day
自己紹介
石川達也
株式会社Codeer代表取締役
C, C++, C#, Java
Exceptional C++ 読書会で
常識のある人間です。
Exceptional C++ 読書会で
常識のある人間です。
・gotoを使わない。
Exceptional C++ 読書会で
常識のある人間です。
・gotoを使わない。・Duff ‘s deviceなどもっての外。
Exceptional C++ 読書会で
常識のある人間です。
・gotoを使わない。・Duff ‘s deviceなどもっての外。・VC++をDisらない。
今日は、体験談を語らせてもらいます。
参考にするもよし、マサカリ投げるもよし。(優しく)
アジェンダ
・例外
・テンプレート活用例・関数ポインタキャスト・アロケータ記憶・AOPもどきログ
例外
・リソース系
・プログラムミス系
注)OutOfMemory場合によって・・・
リソース系or
プログラムミス系
例外
・リソース系FileIO他の機器との通信
例外安全に作ります。最終的には、catchして適切に処理。て言うか、例外とは思っていない。
例外に関する姿勢
・プログラムミス(ASSERT含む)
問題はこれ。
例外に関する姿勢
std::vector<int> buf;・・・buf[index] = 100; //範囲外とか。
例外(プログラムミス)
swtich(val) {・・・default://予期せぬ値とか。
ASSERT(FALSE);break;
}
例外(プログラムミス)
可能な限りの終了処理をしてプロセスを停止させる。
注)お客様との調整が必須。
例外(プログラムミス)
堅牢性と正当性は相反する。
例外(プログラムミス)
size_t index = ・・・;std::vector<int> buf;・・・ASSERT_DEAD(index < buf.size());buf[index] = 100;
例外(プログラムミス)
swtich(val) {・・・default:
ASSERT_DEAD(FALSE);}
例外(プログラムミス)
例外(プログラムミス)
そのまま動作させると・・・
・永続データを壊すかも。
・原因特定困難な不具合になる。
・使われない復帰コードでコードが腐敗し、さらなる不具合を招く。
例外(プログラムミス)
潔く終了すると・・・。
・変なデータができない。
・不具合解析が楽。
・コードはスッキリ。↓
結果的にリリース品質が劇的にUP。
例外(プログラムミス)
あ、
「この辺は無理に終了しなくても・・・」とかは無し。
その切り分けは難しく、だんだん、生き恥をさらす方向に行くので。
次
テンプレート活用例
①関数ポインタキャスト
FARPROC GetProcAddress(
HMODULE hModule, LPCSTR lpProcName);
おなじみ、DLL関数取得。
①関数ポインタキャスト
関数のロードが面倒・・・。
typedef void (__stdcall *FuncType)(int value);FuncType Func = NULL;
Func = (Func)GetProcAddress(h, “Func”);
①関数ポインタキャスト
ちょっとしたユーティリティーを作っておく。
template<typename T>void GetEx(HMODULE hModule, LPCSTR name,
T& func) {
func = (T)GetProcAddress(h, name);}
#define GETPROC(h, f) GetEx(h, #f, f)
①関数ポインタキャスト
スッキリ!
void (__stdcall *Func)(int value);
GETPROC(h, Func);
①関数ポインタキャスト
①関数ポインタキャスト
ついこないだも、使っちゃいました。
→コードへ。http://www.codeer.co.jp/technical-notes/NativeAndNet
②アロケータ記憶
DLLで、ランタイム異なることありますよね・・・?
(゚◇゚)共通にすれば済む話・・・。
Exe dll
メモリ管理メモリ管理
そうすると、ヒープも違うんですよね。
②アロケータ記憶
混ぜるな危険
②アロケータ記憶
でも、動的なコンテナ使いたいんです。
②アロケータ記憶
で、コンテナ作りました。
Σ⊂( ̄□ ̄~j
template<typename T>
class ArrayAllocator
{
public:
T* (__stdcall *New)(unsigned int size);
void (__stdcall *Delete)(T* ptr);
ArrayAllocator() : New(NewCore),Delete(DeleteCore){}
②アロケータ記憶
private:
static T* __stdcall NewCore(unsigned int size)
{
return new T[size];
}
static void __stdcall DeleteCore(T* ptr)
{
if (ptr)
{
delete[] ptr;
}
}};
//配列template<typename T>
class Array
{
ArrayAllocator<T> _heap;
・・・};
//文字列class WString
{
Array<WCHAR> _core;
・・・};
struct Data
{
Array<int> ar;
WString str;
};
void _stdcall Func(Array<Data>& a)
{
//データを詰める}
入れ子もOK!
{Array<Data> a;Func(a);
}抜けたら、それぞれのメモリ管理のdeleteが呼ばれる。
Void Func(Array<Data>& a){
a.resize(100);a[99].ar.resize(100);a[99].str = L“abc”;
②アロケータ記憶
③AOPもどきでログを仕込む
関数の呼び出し順をログ出力したことありますよね?
③AOPもどきでログを仕込む
全部に、いちいち仕込むのは面倒!
横断的(AOP)に処理したい!
template <typename Ret, int fileNo, int lineNo>
struct AOPLog {
//DLL関数の型typedef Ret (__stdcall *FuncType)();
//ログstatic std::string& Log()
{ static std::string log; return log; }
//DLL関数static FuncType& Func()
{ static FuncType func; return func; }
//ログ出力と呼び出しstatic Ret __stdcall Invoke()
{ Print(Log()); return Func(); }
};
//戻り値voidで特殊化template <int fileNo, int lineNo>
struct AOPLog<void, fileNo, lineNo> {
typedef void(__stdcall *FuncType)();
static std::string& Log()
{ static std::string log; return log; }
static FuncType& Func()
{ static FuncType func; return func; }
static void __stdcall Invoke()
{ Print(Log()); Func(); }
};
//ログ入り関数ロードtemplate<int fileNo, int lineNo, typename Ret>
void MakeLog(HMODULE h,
Ret (__stdcall *&func)(), LPCSTR name)
{
typedef AOPLog<Ret, fileNo, lineNo> T;
if (s_logMode) {
T::Func() =
(T::FuncType)::GetProcAddress(h,name);
T::Log() = funcName;
func = T::Invoke;
} else {
func = (T::FuncType)::GetProcAddress(h,name);
}
}
//今のを引数が必要分繰り返す。//BOOST_PPとか。
//でヘルパマクロ#define LOG_GETEX(fileNo, h, f) \
MakeLog<fileNo, __LINE__>(h, f, #f)
//使うところは、すっきり。void (__stdcall *Func)();
LOG_GETEX(0, h, Func);
そろそろ時間!
ご清聴ありがとうございました。