web-dev-qa-db-ja.com

C ++の関数内で構造とクラスを定義できるのはなぜですか?

私は誤ってC++でこのようなことをしましたが、動作します。なぜこれを行うことができますか?

int main(int argc, char** argv) {
    struct MyStruct
    {
      int somevalue;
    };

    MyStruct s;
    s.somevalue = 5;
}

今、これを行った後、私はこのトリックをC++用の貧しい人の機能プログラミングツールの一種としてどこかで読んだことを覚えていましたが、これがなぜ有効なのか、どこで読んだのか覚えていません。

どちらの質問への回答も歓迎します!

注:質問を書いているときに この質問 への参照を取得できませんでしたが、現在のサイドバーはそれを指摘しているので、参照用にここに配置します。役に立つかもしれません。

80
Robert Gould

[EDIT 18/4/2013]:幸いなことに、C++ 11では下記の制限が解除されたため、ローカルで定義されたクラスは結局役に立ちます!コメンテーター竹のおかげで。

クラスをローカルで定義する機能wouldは、カスタムファンクター(operator()()を持つクラス、たとえばstd::sort()またはstd::for_each())とともに使用される「ループボディ」は、はるかに便利です。

残念ながら、C++は、テンプレートでローカルに定義されたクラスを使用することを禁止しています。ファンクターのほとんどのアプリケーションには、ファンクタータイプにテンプレート化されたテンプレートタイプが関係するため、ローカルに定義されたクラスをこれに使用することはできません。関数の外部で定義する必要があります。 :(

[EDIT 1/11/2009]

標準からの関連する引用は次のとおりです。

14.3.1/2:。ローカルタイプ、リンケージのないタイプ、名前のないタイプ、またはこれらのタイプのいずれかから複合されたタイプは、テンプレートタイプのテンプレート引数として使用されません-パラメータ。

67
j_random_hacker

ローカルに定義されたC++クラスの1つのアプリケーションは、 工場設計パターン にあります。


// In some header
class Base
{
public:
    virtual ~Base() {}
    virtual void DoStuff() = 0;
};

Base* CreateBase( const Param& );

// in some .cpp file
Base* CreateBase( const Params& p )
{
    struct Impl: Base
    {
        virtual void DoStuff() { ... }
    };

    ...
    return new Impl;
}

匿名名前空間でも同じことができますが。

28

まあ、基本的に、なぜですか? Cのstruct(時間の夜明けに戻る)は、レコード構造を宣言するための単なる方法でした。必要な場合は、単純な変数を宣言する場所で宣言できないのはなぜですか?

それを行ったら、C++の目標は、可能な限りCと互換性があることであることを忘れないでください。それで留まりました。

9
Charlie Martin

たとえば、「7.8:ローカルクラス:関数内のクラス」のセクションで言及されています http://www.icce.rug.nl/documents/cplusplus/cplusplus07.html ローカルクラス」、「継承やテンプレートを含む高度なアプリケーションで非常に役立つ可能性がある」と言います。

5
ChrisW

実際、スタックベースの例外安全作業を行うのに非常に便利です。または、複数の戻り点を持つ関数からの一般的なクリーンアップ。これはよくRAII(リソースの取得は初期化)イディオムと呼ばれます。

void function()
{

    struct Cleaner
    {
        Cleaner()
        {
            // do some initialization code in here
            // maybe start some transaction, or acquire a mutex or something
        }

        ~Cleaner()
        {
             // do the associated cleanup
             // (commit your transaction, release your mutex, etc.)
        }
    };

    Cleaner cleaner();

    // Now do something really dangerous
    // But you know that even in the case of an uncaught exception, 
    // ~Cleaner will be called.

    // Or alternatively, write some ill-advised code with multiple return points here.
    // No matter where you return from the function ~Cleaner will be called.
}
5
simong

適切に初期化されたオブジェクトの配列を作成するためです。

デフォルトのコンストラクタを持たないクラスCがあります。クラスCのオブジェクトの配列が必要です。これらのオブジェクトをどのように初期化したいのかを把握し、DのデフォルトコンストラクターでCの引数を提供する静的メソッドでCからクラスDを派生させます。

#include <iostream>
using namespace std;

class C {
public:
  C(int x) : mData(x)  {}
  int method() { return mData; }
  // ...
private:
  int mData;
};

void f() {

  // Here I am in f.  I need an array of 50 C objects starting with C(22)

  class D : public C {
  public:
    D() : C(D::clicker()) {}
  private:
    // I want my C objects to be initialized with consecutive
    // integers, starting at 22.
    static int clicker() { 
      static int current = 22;
      return current++;
    } 
  };

  D array[50] ;

  // Now I will display the object in position 11 to verify it got initialized
  // with the right value.  

  cout << "This should be 33: --> " << array[11].method() << endl;

  cout << "sizodf(C): " << sizeof(C) << endl;
  cout << "sizeof(D): " << sizeof(D) << endl;

  return;

}

int main(int, char **) {
  f();
  return 0;
}

簡単にするために、この例では、単純なデフォルト以外のコンストラクターと、コンパイル時に値がわかっている場合を使用します。この手法を、実行時にのみ既知の値で初期化されたオブジェクトの配列が必要な場合に拡張するのは簡単です。

3