C++ で歩哨クラスを使用するのが好きですが、次のようなバグを繰り返し書いてしまう精神的苦痛があるようです。
{
MySentryClass(arg);
// ... other code
}
言うまでもなく、セントリーは意図したようにスコープの最後ではなく、作成直後に死ぬため、これは失敗します。上記のコードがコンパイルに失敗するか、少なくとも実行時にエラー メッセージで中止されるように、MySentryClass が一時的にインスタンス化されるのを防ぐ方法はありますか?
C++ で歩哨クラスを使用するのが好きですが、次のようなバグを繰り返し書いてしまう精神的苦痛があるようです。
{
MySentryClass(arg);
// ... other code
}
言うまでもなく、セントリーは意図したようにスコープの最後ではなく、作成直後に死ぬため、これは失敗します。上記のコードがコンパイルに失敗するか、少なくとも実行時にエラー メッセージで中止されるように、MySentryClass が一時的にインスタンス化されるのを防ぐ方法はありますか?
この間違いを犯したかどうかを自動的に検出する方法は考えられません。正しいものに展開するマクロをいつでも作成し、それを使用してセントリーを宣言することができます。
#define MY_SENTRY_CLASS(_X) MySentryClass _sentry(_X)
そして使用する
MY_SENTRY_CLASS(arg);
または、ポストイットをモニターに貼って思い出させてください。
実行できる唯一のことは、コンストラクターをプライベートにし、ヘルパー関数を介してアクセスを強制することです。これは、最初の構築構文よりもはるかに類似しておらず、誤解される可能性が低くなります。ヒープに割り当てることもできますが(それでも無駄です)、見つけるのははるかに簡単です。ただし、クラスを構築可能にしたい場合は、そのタイプの右辺値を作成する人々を止めることはできません。
編集:MySentryClassが常に引数を取ることがわかっている場合は、構築ANDを禁止し、operator =(arguments)のみを許可することができます。これはあなたにやらなければならないでしょう
MySentryClass x;
x = arg;
あなたはそれのためにある種のメソッドチェーンを行うことができます。
MySentryClass x;
x.SetArg1(arg).SetArg2(arg2).construct();
いいえ、この問題からの出口はありません。スタック上にオブジェクトを作成するには、パブリックコンストラクターが必要です。パブリックコンストラクターがある場合は、報告している間違いを犯す可能性があります。
この解決策が気に入るかどうかはわかりませんが、解決策は次のようになる可能性がありますgrep
。
find /path/to/project -type f -name \*.cpp -print0 | xargs grep -0 'MySentryClass('
sed
またはを使用perl
してソース ファイルを前処理し、 に置き換えるMySentryClass(
と\n#error MySentryClass used incorrectly\n
、エラーが発生した場所に近い行番号が得られることを願っています。これを行う方法は、ビルド システムによって異なります。
#define が最良の方法だと思います。
ただし、#define を使用しないためのオプションとして:
int main()
{
try
{
S arg1;
// This will not compile
// MySentry x1 = MySentry::CreateSentry(arg1);
S arg3;
MySentry x2(MySentry::CreateSentry(arg3));
S arg2;
// This will not compile
// MySentry(arg2);
S arg4;
// This will generate a runtime exception
// It will never call start() or end()
//MySentry::CreateSentry(arg4);
}
catch(std::exception const& e)
{
std::cout << "Exception : " << e.what() << "\n";
}
}
#include <stdexcept>
#include <iostream>
class S
{
public:
void start() {std::cout << "Start\n";}
void end() {std::cout << "End\n";}
};
class MySentry
{
struct Init
{
Init(S& s) : arg(s),bad(true) {}
~Init() {if (bad) {throw std::runtime_error("Bad usage of MySentry");}}
S& arg;
mutable bool bad;
};
public:
static Init CreateSentry(S& arg) { return Init(arg);}
explicit MySentry(Init const& arg)
: obj(arg.arg)
, bad(false)
{
arg.bad = false;
std::cout << "Created\n";
obj.start();
}
MySentry(MySentry const& rhs)
: obj(rhs.obj)
, bad(false)
{
std::cout << "Copied (this may not appear)\n";
std::cout << "If the optimizer kicks in then the copy may be elided.\n";
// But if it did not optimize out then
// We have to mark the temporaty as bad
// And not call end() in its destructor.
// Note: Never call start() here as it will always be called in the
// main private constrctor above
rhs.bad = true;
}
~MySentry()
{
if (!bad)
{
// Everything working
obj.end();
}
std::cout << "Destroyed\n";
}
private:
S& obj;
mutable bool bad;
};
あなたがやろうとしていることは C++ では完全に合法であり、それを禁止する方法はないと思います。