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


90

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

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

    MyStruct s;
    s.somevalue = 5;
}

これを行った後、私は昔、C ++用の貧乏人の関数型プログラミングツールの一種として、このトリックについてどこかで読んだことを思い出しましたが、なぜこれが有効なのか、どこで読んだのか思い出せません。

どちらの質問への回答も大歓迎です!

注:質問を書いているとき、この質問への参照はありませんでしたが、現在のサイドバーがそれを指摘しているので、参照用にここに置きます。どちらにしても、質問は異なりますが、役立つ場合があります。


回答:


70

[2013年4月18編集]:幸い、C ++ 11では以下の制限が解除されたため、ローカルで定義されたクラスが便利です。コメント投稿者bamboonに感謝します。

ローカルクラスを定義することでしょう(とクラスカスタムファンクタを作成作るoperator()()に渡すため、例えば比較関数をstd::sort()一緒に使用するか、「ループボディ」std::for_each())はるかに便利。

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

[2009年1月11日編集]

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

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


2
経験的には、これはMSVC ++ 8で機能するようです。(ただし、g ++ではありません。)
j_random_hacker 2009年

私はgcc4.3.3を使用していますが、そこでは機能しているようです:pastebin.com/f65b876b。規格で禁止されている場所についての言及はありますか?使用時に簡単にインスタンス化できるように思えます。
Catskul 2009年

@Catskul:14.3.1 / 2:「ローカル型、リンケージのない型、名前のない型、またはこれらの型のいずれかから複合された型は、テンプレート型パラメータのテンプレート引数として使用しないでください」。ローカルクラスでは、さらに別の情報をマングル名にプッシュする必要があるというのが理論的根拠だと思いますが、それは確かではありません。もちろん、MSVC ++ 8や最近のバージョンのg ++​​が提供しているように、特定のコンパイラがこれを回避するための拡張機能を提供している場合があります。
j_random_hacker 2009年

9
この制限はC ++ 11で解除されました。
Stephan

31

ローカルで定義された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;
}

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


面白い!私が言及したテンプレートに関する制限が適用されますが、このアプローチは、CreateBase()を除いて、Implのインスタンスを作成できない(または話し合うことさえできない)ことを保証します。したがって、これは、クライアントが実装の詳細に依存する程度を減らすための優れた方法のように思われます。+1。
j_random_hacker 2009年

26
それは素晴らしいアイデアです。すぐに使用するかどうかはわかりませんが、バーで引っ張ってひよこを感動させるのにおそらく良いアイデアです:)
ロバートグールド

2
(私はひよこについて話しているのですが、答えではありません!)
markh44 2009年

9
笑ロバート...うん、何もかなりC ++の曖昧なコーナーについて知るような女性を印加しない...
j_random_hacker

10

実際には、スタックベースの例外安全性作業を行うのに非常に役立ちます。または、複数のリターンポイントを持つ関数からの一般的なクリーンアップ。これはしばしば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
Cleaner cleaner();これはオブジェクト定義ではなく関数宣言になると思います。
ユーザー

2
@userあなたは正しいです。デフォルトのコンストラクターを呼び出すには、Cleaner cleaner;またはを記述する必要がありCleaner cleaner{};ます。
callyalater 2016

関数内のクラスはRAIIとは何の関係もありません。さらに、これは有効なC ++コードではなく、コンパイルされません。
ミハイルヴァシリエフ

1
関数内でさえ、このようなクラスはまさにC ++のRAIIのすべてです。
クリストファーブランズ

9

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

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


生き残った素晴らしい機能の一種ですが、j_random_hackerが指摘したように、C ++で想像していたほど有用ではありません:/
Robert Gould

ええ、Cでもスコープルールがおかしかったです。私はC ++で25年以上の経験を積んでいるので、Cと同じように努力するのは間違いだったのではないかと思います。一方、エッフェル塔のようなよりエレガントな言語は、それほど簡単には採用されませんでした。
チャーリーマーティン

はい、既存のCコードベースをC ++に移行しました(ただし、Eiffelには移行していません)。
ChrisW 2009年

5

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


3

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

デフォルトのコンストラクターがないクラス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;
}

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


確かに面白いアプリケーションです!ただし、それが賢明で安全かどうかはわかりません。Dの配列をCの配列として扱う必要がある場合(たとえば、D*パラメーターを受け取る関数に渡す必要がある場合)、Dが実際にCより大きい場合、これは黙って壊れます。 。(私は思う...)
j_random_hacker 2013

+ j_random_hacker、sizeof(D)== sizeof(C)。sizeof()レポートを追加しました。
Thomas L Holaday 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.