一時を禁止する方法


107

クラスFooに、名前を付けずに構築を禁止する方法はありますか?

例えば:

Foo("hi");

そして、次のような名前を付けた場合にのみ許可しますか?

Foo my_foo("hi");

最初のライフタイムは単にステートメントであり、2番目のライフタイムは囲みブロックです。私の使用例でFooは、コンストラクタとデストラクタの間の時間を測定しています。ローカル変数を参照することはないので、ローカル変数を入れ忘れて、誤って有効期間を変更してしまうことがよくあります。代わりにコンパイル時エラーが発生します。


8
これは、ミューテックスロックガードにも役立ちます。
lucas clemente 2012

1
まあ、禁止されている場所で独自のC ++コンパイラを作成することはできますが、厳密に言えば、その場合はC ++ではありません。そのような一時が有用であろう場所は、(のようなたとえば、関数からオブジェクトを返すときのように、もありますreturn std::string("Foo");
いくつかのプログラマーの男

2
いいえ、できません。申し訳ありません
Armen Tsirunyan 2012年

2
あなたの宗教によっては、これはマクロが便利になるケースかもしれません(常に変数を作成するマクロを介してのみそのタイプを使用することによって)
PlasmaHH '31 / 10/14

3
コンパイラーのハックによって構文的に防止したいものよりも、LINTツールでキャッチしたいものに似ています。
ウォーレンP

回答:


101

別のマクロベースのソリューション:

#define Foo class Foo

ステートメントFoo("hi");はに展開class Foo("hi");されますが、これは形式が正しくありません。しかし、にFoo a("hi")展開されclass Foo a("hi")ます。これは正しいことです。

これには、既存の(正しい)コードとソース互換性およびバイナリ互換性があるという利点があります。(この主張は完全に正しいわけではありません。以下のJohannes Schaubのコメントとその後のディスカッションを参照してください:「既存のコードとソース互換性があることをどうやって知ることができますか?彼の友人は彼のヘッダーを含み、void f(){int Foo = 0;}を持っています。以前は問題なくコンパイルされ、現在は誤ってコンパイルされています!また、クラスFooのメンバー関数を定義するすべての行が失敗します:void class Foo :: bar(){} "


51
既存のコードとソース互換性があることをどのようにして知ることができますか?彼の友人は彼のヘッダーを含み、void f() { int Foo = 0; }以前は問題なくコンパイルされ、現在は正しくコンパイルされていません!また、クラスFooのメンバー関数を定義するすべての行は失敗しますvoid class Foo::bar() {}
Johannes Schaub-litb 2012年

21
どうしてこれほど多くの票を獲得できるでしょうか?@ JohannesSchaub-litbのコメントを見るだけで、これが本当に悪い解決策であることがわかります。この後、メンバー関数の定義はすべて無効になるからです。私の側から-1
Aamir

2
@JustMaximumPower:それが皮肉なことではないことを願っています。定義を解除するとスクエア1に戻るので、同様の行、つまりFoo("Hi")Foo.cppの内部でコンパイルエラー(OPが意図したもの)が発生しないことになります
Aamir

1
@アーミルいいえ私は真剣です。Martin C. Martinは、実装ではなくFooの使用を保護するためにそれを使用するつもりです。
JustMaximumPower

1
Visual Studio 2012で試しましたclass Foo("hi");が、コンパイルしても問題ありません。
fresky

71

少しハックはどうですか

class Foo
{
    public:
        Foo (const char*) {}
};

void Foo (float);


int main ()
{
    Foo ("hello"); // error
    class Foo a("hi"); // OK
    return 1;
}

1
グレートハック!注:(Foo a("hi");なしclass)もエラーになります。
ビットマスク、

私は上手く理解できていない気がします。Foo( "hello")がvoid Foo(float)を呼び出そうとすると、リンカエラーが発生しますか?しかし、なぜFoo ctorの代わりにfloatバージョンが呼び出されるのですか?
undu 2012年

2
undu、hmどのコンパイラを使用していますか?gcc 3.4は、floatへの変換がないと不平を言います。Fooクラスよりも優先されるため、関数を呼び出そうとします。

@aleguna実際には私はこのコードを実行しようとはしませんでした。それは単なる(悪い)推測です:sしかし、とにかく私の質問に答えました。
undu

1
@didiercいいえ、Foo::Foo("hi")C ++では許可されていません。
Johannes Schaub-litb 2012年

44

コンストラクターをプライベートにしますが、クラスにcreateメソッドを与えます


9
-1:これはOPの問題をどのように解決するのですか?あなたはまだ書くことができますFoo::create();オーバーFoo const & x = Foo::create();
トーマスEding

@ThomasEdingあなたが正しいと思います、それはOPのコア問題を修正しませんが、彼に考えさせ、間違いを犯さないようにします。
dchhetri 2012年

1
@ThomasEdingは、システムを壊したい怒っているユーザーから身を守ることはできません。@ecatmurのハックがあっても、あなたは言うstd::common_type<Foo>::type()ことができ、一時的なものになります。またはtypedef Foo bar; bar()
ヨハネスシャウブ-litb

@ JohannesSchaub-litb:しかし、大きな違いは、それが誤っていたかどうかです。タイピングstd::common_type<Foo>::type()が間違っていることはほとんどありません。Foo const & x = ...偶然に立ち去ることは完全に信じられます。
Thomas Eding、2012年

24

これはコンパイラエラーにはなりませんが、ランタイムエラーになります。間違った時間を測定する代わりに、許容できる例外が発生します。

保護するコンストラクタにset(guard)は、呼び出されるデフォルトの引数が必要です。

struct Guard {
  Guard()
    :guardflagp()
  { }

  ~Guard() {
    assert(guardflagp && "Forgot to call guard?");
    *guardflagp = 0;
  }

  void *set(Guard const *&guardflag) {
    if(guardflagp) {
      *guardflagp = 0;
    }

    guardflagp = &guardflag;
    *guardflagp = this;
  }

private:
  Guard const **guardflagp;
};

class Foo {
public:
  Foo(const char *arg1, Guard &&g = Guard()) 
    :guard()
  { g.set(guard); }

  ~Foo() {
    assert(!guard && "A Foo object cannot be temporary!");
  }

private:
  mutable Guard const *guard;
}; 

特徴は次のとおりです。

Foo f() {
  // OK (no temporary)
  Foo f1("hello");

  // may throw (may introduce a temporary on behalf of the compiler)
  Foo f2 = "hello";

  // may throw (introduces a temporary that may be optimized away
  Foo f3 = Foo("hello");

  // OK (no temporary)
  Foo f4{"hello"};

  // OK (no temporary)
  Foo f = { "hello" };

  // always throws
  Foo("hello");

  // OK (normal copy)
  return f;

  // may throw (may introduce a temporary on behalf of the compiler)
  return "hello";

  // OK (initialized temporary lives longer than its initializers)
  return { "hello" };
}

int main() {
  // OK (it's f that created the temporary in its body)
  f();

  // OK (normal copy)
  Foo g1(f());

  // OK (normal copy)
  Foo g2 = f();
}

の場合f2f3およびの返品を"hello"希望しない場合があります。スローを防ぐために、コピーguardのソースではなく保護するようにをリセットすることにより、コピーのソースを一時的なものにすることができます。これで、上記のポインターを使用した理由もわかります。これにより、柔軟性を高めることができます。

class Foo {
public:
  Foo(const char *arg1, Guard &&g = Guard()) 
    :guard()
  { g.set(guard); }

  Foo(Foo &&other)
    :guard(other.guard)
  {
    if(guard) {
      guard->set(guard);
    }
  }

  Foo(const Foo& other)
    :guard(other.guard)
  {
    if(guard) {
      guard->set(guard);
    }
  }

  ~Foo() {
    assert(!guard && "A Foo object cannot be temporary!");
  }

private:
  mutable Guard const *guard;
}; 

以下のための特性f2f3およびのためには、return "hello"常になりました// OK


2
Foo f = "hello"; // may throwこれは、このコードを使用しないように私を怖がらせるのに十分です。
Thomas Eding

4
@thomas、コンストラクタにマークを付けるexplicitと、そのようなコードはコンパイルできなくなります。目標は一時的なものを防ぐことでした。怖い場合は、コピーまたは移動コンストラクターでコピーのソースを非一時的なものに設定することで、スローしないようにすることができます。その後、一時的なものである場合は、いくつかのコピーの最後のオブジェクトのみがスローされます。
Johannes Schaub-litb

2
我が神よ。私はC ++とC ++ 11の初心者ではありませんが、これがどのように機能するのか理解できません。いくつか説明を追加
ミハイル

6
@Mikhail同じ時点で破棄される一時オブジェクトの破棄の順序は、それらの構築の逆の順序です。呼び出し元が渡すデフォルトの引数は一時的なものです。場合はFoo、オブジェクトは、あまりにも一時的なものであり、デフォルトの引数と同じ表現でその生涯端は、その後、Foo前者は後者の後に作成されたため、オブジェクトのデストラクタは、デフォルト引数のデストラクタの前に呼び出されます。
Johannes Schaub-litb

1
@ JohannesSchaub-litbとても素敵なトリック。との中Foo(...);とを区別するのは不可能だと本当に思っFoo foo(...);Foo
ミハイル

18

数年前、私はその状況に対して新しい警告オプションを追加するGNU C ++コンパイラのパッチを書きまし。これは、Bugzillaアイテムで追跡されます

残念ながら、GCC Bugzillaは、十分に考慮されたパッチを含む機能の提案が無効になる埋葬地です。:)

これは、ローカルオブジェクトをガジェットとして使用してロックとロック解除、実行時間の測定などを行うコードで、この質問の主題である種類のバグを正確にキャッチしたいという欲求に基づいています。


9

現状では、実装ではこれを行うことはできませんが、このルールを利用すると有利です。

一時オブジェクトは非const参照にバインドできません

コードをクラスから、非const参照パラメーターを取る独立した関数に移動できます。その場合、一時的に非const参照にバインドしようとすると、コンパイラエラーが発生します。

コードサンプル

class Foo
{
    public:
        Foo(const char* ){}
        friend void InitMethod(Foo& obj);
};

void InitMethod(Foo& obj){}

int main()
{
    Foo myVar("InitMe");
    InitMethod(myVar);    //Works

    InitMethod("InitMe"); //Does not work  
    return 0;
}

出力

prog.cpp: In function int main()’:
prog.cpp:13: error: invalid initialization of non-const reference of type Foo&’ from a temporary of type const char*’
prog.cpp:7: error: in passing argument 1 of void InitMethod(Foo&)’

1
@didierc:追加機能を提供する場合、それを行わないのはユーザーの責任です。標準で明示的に許可されていないものを実現する方法を微調整しようとしているため、当然ながら制限があります。
Alokが2012

@didiercパラメータxは名前付きオブジェクトであるため、本当に禁止するかどうかは不明です。使用したコンストラクタが明示的である場合、人々は本能的にそうするかもしれませんFoo f = Foo("hello");。失敗すれば怒るだろう。私のソリューションは、最初に例外(および非常に類似したケース)でそれを拒否し、例外/アサートの失敗で誰かが不平を言いました。
Johannes Schaub-litb

@ JohannesSchaub-litbはい、OPはバインディングを強制することにより、コンストラクターによって生成された値の破棄を禁止します。私の例は間違っています。
didierc

7

単にデフォルトのコンストラクターがなく、すべてのコンストラクターのインスタンスへの参照が必要です。

#include <iostream>
using namespace std;

enum SelfRef { selfRef };

struct S
{
    S( SelfRef, S const & ) {}
};

int main()
{
    S a( selfRef, a );
}

3
ニースのアイデアが、あなたは一つの変数を持っているとすぐに:S(selfRef, a);。:/
Xeo 2012年

3
@Xeo S(SelfRef, S const& s) { assert(&s == this); }、実行時エラーが許容できる場合。

6

いいえ、できません。しかし、マクロを作成することで同じ効果を得ることができます。

#define FOO(x) Foo _foo(x)

これを実行すると、Foo my_foo(x)の代わりにFOO(x)と書くことができます。


5
私は賛成票を投じようとしていましたが、「マクロを作成できます」ということがわかりました。
Griwes、2012年

1
アンダースコアを修正しました。@Griwes-原理主義者にならないでください。「これはできません」というより「マクロを使う」と言ったほうがいいです。
amaurea 2012年

5
まあ、それはできません。あなたはまったく問題を解決していませんFoo();。それを行うことは完全に合法です。
パピー

11
今、あなたはここで頑固です。Fooクラスの名前を複雑なものに変更し、マクロFooを呼び出します。問題が解決しました。
amaurea 2012年

8
例:class Do_not_use_this_class_directly_Only_use_it_via_the_FOO_macro;
ベンジャミンリンドリー2012年

4

主な目的はバグを防ぐことなので、次の点を考慮してください。

struct Foo
{
  Foo( const char* ) { /* ... */ }
};

enum { Foo };

int main()
{
  struct Foo foo( "hi" ); // OK
  struct Foo( "hi" ); // fail
  Foo foo( "hi" ); // fail
  Foo( "hi" ); // fail
}

そうすれば、変数に名前を付けることを忘れず、を書くことも忘れられませんstruct。冗長ですが安全です。


1

1パラメータコンストラクタを明示的に宣言すると、そのクラスのオブジェクトが意図せずに作成されることはありません。

例えば

class Foo
{
public: 
  explicit Foo(const char*);
};

void fun(const Foo&);

この方法でのみ使用できます

void g() {
  Foo a("text");
  fun(a);
}

しかし、これは決してありません(スタック上の一時的なものを介して)

void g() {
  fun("text");
}

参照:Alexandrescu、C ++コーディング標準、アイテム40。


3
これは可能fun(Foo("text"));です。
ギルヘルムベルナル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.