108

クラスFooの場合、名前を付けずに構築できないようにする方法はありますか?

例えば:

Foo("hi");

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

Foo my_foo("hi");

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

4

10 に答える 10

100

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

#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() {}" )

于 2012-10-31T14:32:10.430 に答える
71

ちょっとしたハックはどうですか

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

void Foo (float);


int main ()
{
    Foo ("hello"); // error
    class Foo a("hi"); // OK
    return 1;
}
于 2012-10-31T14:08:15.243 に答える
42

コンストラクターを非公開にしますが、クラスにcreateメソッドを与えます。

于 2012-10-31T14:33:52.720 に答える
25

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

保護するコンストラクターには、が呼び出されるデフォルトの引数が必要です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です。

于 2012-10-31T23:00:49.940 に答える
19

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

残念ながら、GCC Bugzilla は、よく考えられたパッチに含まれる機能の提案が死んでしまう埋葬地です。:)

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

于 2012-10-31T16:46:43.123 に答える
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&)’
于 2012-10-31T14:10:22.880 に答える
7

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

#include <iostream>
using namespace std;

enum SelfRef { selfRef };

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

int main()
{
    S a( selfRef, a );
}
于 2012-10-31T23:01:08.080 に答える
6

いいえ、残念ながらこれは不可能です。しかし、マクロを作成することで同じ効果を得ることができます。

#define FOO(x) Foo _foo(x)

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

于 2012-10-31T14:04:19.860 に答える
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. 冗長ですが、安全です。

于 2013-02-18T23:15:25.323 に答える
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。

于 2012-11-06T19:37:38.553 に答える