web-dev-qa-db-ja.com

C ++で「スーパー」を使用する

私のコーディングスタイルには、次のイディオムが含まれています。

class Derived : public Base
{
   public :
      typedef Base super; // note that it could be hidden in
                          // protected/private section, instead

      // Etc.
} ;

これにより、たとえばコンストラクターでBaseのエイリアスとして「スーパー」を使用できます。

Derived(int i, int j)
   : super(i), J(j)
{
}

または、オーバーライドされたバージョン内の基本クラスからメソッドを呼び出す場合でも:

void Derived::foo()
{
   super::foo() ;

   // ... And then, do something else
}

チェーン化することもできます(ただし、その使用方法を見つける必要があります)。

class DerivedDerived : public Derived
{
   public :
      typedef Derived super; // note that it could be hidden in
                             // protected/private section, instead

      // Etc.
} ;

void DerivedDerived::bar()
{
   super::bar() ; // will call Derived::bar
   super::super::bar ; // will call Base::bar

   // ... And then, do something else
}

とにかく、たとえばBaseが冗長である場合やテンプレート化されている場合など、「typedef super」の使用は非常に便利です。

実際、superはC#と同様にJavaでも実装されています(私が間違っていない限り、「ベース」と呼ばれます)。しかし、C++にはこのキーワードがありません。

だから、私の質問:

  • typedefのこの使用は、使用するコードで見られることはほとんどありません。
  • typedef super Okのこの使用はありますか(つまり、使用しない強い理由がありますか?
  • 「スーパー」は良いことなのか、C++でいくらか標準化されるべきなのか、それともtypedefを介した使用はもう十分なのか?

編集: ロディはtypedefがプライベートであるべきであるという事実に言及しました。これは、派生クラスが再宣言しない限り使用できないことを意味します。しかし、super :: super連鎖を防ぐこともできると思います(しかし、だれが泣くのでしょうか?)。

編集2: さて、「スーパー」を大量に使用してから数か月後、私はロディの視点に心から同意します。「スーパー」はプライベートであるべきです。私は彼の答えを2回支持しますが、できないと思います。

188
paercebal

Bjarne StroustrupはC++の設計と進化で、C++が初めて標準化されたときにISO C++標準委員会によってキーワードとしてsuperが検討されたと述べています。

Dag Bruckはこの拡張を提案し、基本クラスを「継承」と呼びました。この提案は多重継承の問題に言及しており、あいまいな使用にフラグを立てるでしょう。 Stroustrupでさえも確信していた。

議論の後、Dag Bruck(はい、提案を行った同じ人物)は、提案が実装可能で、技術的に健全で、重大な欠陥がないことを書き、多重継承を処理しました。一方で、その額に見合うだけの十分な余裕はなかったので、委員会は厄介な問題を処理する必要があります。

Michael Tiemannが遅れて到着し、typedefされたsuperがこの投稿で尋ねられたのと同じテクニックを使用して、うまく機能することを示しました。

だから、いや、これはおそらく標準化されないでしょう。

コピーがない場合は、Design and Evolutionはカバー価格の価値があります。使用済みのコピーは約10ドルで入手できます。

138
Max Lybbert

私は常にスーパーではなく「継承」を使用してきました。 (おそらくDelphiのバックグラウンドが原因です)、私は常にprivateにして、「継承」がクラスから誤って省略されたがサブクラスがそれを使用しようとした場合の問題を回避します。

class MyClass : public MyBase
{
private:  // Prevents erroneous use by other classes.
  typedef MyBase inherited;
...

新しいクラスを作成するための標準の「コードテンプレート」にはtypedefが含まれているため、誤ってそれを省略する機会はほとんどありません。

連鎖した "super :: super"の提案は良いアイディアではないと思います。もしあなたがそうしているなら、おそらく特定の階層に非常に固く結びついているでしょう。

94
Roddy

これに関する問題の1つは、派生クラスのsuperを(再)定義するのを忘れた場合、super :: somethingの呼び出しはすべて正常にコンパイルされますが、おそらく目的の関数を呼び出さないことです。

例えば:

class Base
{
public:  virtual void foo() { ... }
};

class Derived: public Base
{
public:
    typedef Base super;
    virtual void foo()
    {
        super::foo();   // call superclass implementation

        // do other stuff
        ...
    }
};

class DerivedAgain: public Derived
{
public:
    virtual void foo()
    {
        // Call superclass function
        super::foo();    // oops, calls Base::foo() rather than Derived::foo()

        ...
    }
};

(この答えに対するコメントでMartin Yorkが指摘したように、この問題はtypedefをpublicまたはprotectedではなくprivateにすることで解消できます。)

33

FWIW Microsoftは、コンパイラに __ super の拡張機能を追加しました。

17
krujos

Super(または継承)は非常に良いことです。BaseとDerivedの間に別の継承レイヤーを固定する必要がある場合、変更する必要があるのは次の2つだけです。1.「クラスBase:foo」と2。

私の記憶が正しければ、C++標準委員会はこのキーワードを追加することを検討していました... Michael Tiemannがこのtypedefトリックが機能することを指摘するまで。

多重継承に関しては、プログラマーの制御下にあるので、あなたがやりたいことは何でもできます:たぶんsuper1とsuper2、あるいは何でも。

14
Colin Jensen

別の回避策を見つけました。 typedefアプローチには今日大きな問題があります:

  • Typedefには、クラス名の正確なコピーが必要です。誰かがクラス名を変更してもtypedefを変更しない場合、問題が発生します。

そこで、非常にシンプルなテンプレートを使用して、より良いソリューションを思い付きました。

template <class C>
struct MakeAlias : C
{ 
    typedef C BaseAlias;
};

だから今、代わりに

class Derived : public Base
{
private:
    typedef Base Super;
};

あなたが持っている

class Derived : public MakeAlias<Base>
{
    // Can refer to Base as BaseAlias here
};

この場合、BaseAliasはプライベートではありません。他の開発者に警告するタイプ名を選択することで、不注意な使用を防止しようとしました。

12
paperjam

私は以前にこれを見たことを覚えていませんが、一見したところ、私はそれが好きです。 Ferruccio notesのように、MIの場合はうまく機能しませんが、MIはルールよりも例外であり、有用であるためにどこでも使用できる必要があると言うものは何もありません。

11
Michael Burr

このイディオムが多くのコードで使用されているのを見てきましたが、Boostのライブラリのどこかで見たことさえあると確信しています。ただし、覚えている限り、最も一般的な名前はbaseではなくBase(またはsuper)です。

このイディオムは、テンプレートクラスを操作する場合に特に便利です。例として、次のクラスを考えます( 実際のプロジェクト から):

template <typename TText, typename TSpec>
class Finder<Index<TText, PizzaChili<TSpec> >, PizzaChiliFinder>
    : public Finder<Index<TText, PizzaChili<TSpec> >, Default>
{
    typedef Finder<Index<TText, PizzaChili<TSpec> >, Default> TBase;
    // …
}

面白い名前を気にしないでください。ここで重要な点は、継承チェーンが型引数を使用してコンパイル時のポリモーフィズムを実現することです。残念ながら、これらのテンプレートのネストレベルは非常に高くなります。したがって、読みやすさと保守性のために略語は非常に重要です。

8
Konrad Rudolph

ベースが複雑なテンプレートタイプである場合(たとえば、boost::iterator_adaptorはこれを行います)、時々super_tとして使用されることをよく見ました。

3
James Hopkin

このtypedefの使用は、使用するコードで見られることはほとんどありません/まれ/ありませんか?

私が使用しているC++コードでこの特定のパターンを見たことはありませんが、だからといってそこにないわけではありません。

このtypedef superの使用はOk(つまり、使用しない強い理由がありますか?

(とにかく)多重継承を許可していません。

「スーパー」は良いことでしょう。C++で多少標準化されるべきですか、それとも既にtypedefを介して十分に使用されていますか?

上記の理由(多重継承)のため、いいえ。リストした他の言語で「スーパー」と表示されるのは、単一の継承のみをサポートしているため、「スーパー」が参照しているものに関して混乱がないためです。確かに、これらの言語ではISは便利ですが、実際にはC++データモデルに場所がありません。

ああ、そして参考までに:C++/CLIは "__super"キーワードの形式でこの概念をサポートしています。ただし、C++/CLIも多重継承をサポートしていないことに注意してください。

3
Toji

Turbo PascalからC++に以前に移行した後、同じ方法でTurbo Pascalの「継承」キーワードと同等のものを得るためにこれを実行していました。しかし、C++で数年間プログラミングした後、私はそれをやめました。あまり必要ないことがわかりました。

2
Greg Hewgill

スーパークラスにtypedefを使用するもう1つの理由は、オブジェクトの継承で複雑なテンプレートを使用している場合です。

例えば:

template <typename T, size_t C, typename U>
class A
{ ... };

template <typename T>
class B : public A<T,99,T>
{ ... };

クラスBでは、Aのtypedefが理想的です。そうしないと、Aのメンバーを参照したいすべての場所でそれを繰り返してスタックしてしまいます。

これらの場合、複数の継承でも機能しますが、「super」という名前のtypedefがない場合は、「base_A_t」などの名前が付けられます。

--jeffk ++

2
jdkoftinoff

このまったく同じ問題を解決しようとしていました。可変個のテンプレートやパック拡張を使用して、任意の数の親を許可するなど、いくつかのアイデアを思いつきましたが、その結果、「super0」や「super1」のような実装になることに気付きました。そもそもそれを持たないよりもかろうじて役に立つからだ。

私のソリューションにはヘルパークラスPrimaryParentが含まれ、次のように実装されます。

template<typename BaseClass>
class PrimaryParent : virtual public BaseClass
{
protected:
    using super = BaseClass;
public:
    template<typename ...ArgTypes>
    PrimaryParent<BaseClass>(ArgTypes... args) : BaseClass(args...){}
}

次に、使用したいクラスを次のように宣言します。

class MyObject : public PrimaryParent<SomeBaseClass>
{
public:
    MyObject() : PrimaryParent<SomeBaseClass>(SomeParams) {}
}

PrimaryParenton BaseClassで仮想継承を使用する必要を回避するために、可変数の引数を取るコンストラクターを使用して、BaseClassを構築できます。

publicからBaseClassへのPrimaryParent継承の背後にある理由は、MyObjectの間にヘルパークラスがあるにもかかわらず、BaseClassの継承を完全に制御できるようにするためです。 。

つまり、superにしたいすべてのクラスはPrimaryParentヘルパークラスを使用する必要があり、各子はPrimaryParentを使用して1つのクラスからのみ継承できます(そのため)。

このメソッドの別の制限は、MyObjectPrimaryParentから継承する1つのクラスのみを継承でき、そのクラスはPrimaryParentを使用して継承する必要があることです。ここに私が意味するものがあります:

class SomeOtherBase : public PrimaryParent<Ancestor>{}

class MixinClass {}

//Good
class BaseClass : public PrimaryParent<SomeOtherBase>, public MixinClass
{}


//Not Good (now 'super' is ambiguous)
class MyObject : public PrimaryParent<BaseClass>, public SomeOtherBase{}

//Also Not Good ('super' is again ambiguous)
class MyObject : public PrimaryParent<BaseClass>, public PrimaryParent<SomeOtherBase>{}

制限のように見える数と、すべての継承の間に仲介人クラスがあるという事実のために、これをオプションとして破棄する前に、これらのことは悪くありません。

多重継承は強力なツールですが、ほとんどの場合、主な親は1つだけです。他の親が存在する場合、Mixinクラス、またはPrimaryParentを継承しないクラスになる可能性があります。多重継承が依然として必要な場合(多くの場合、継承の代わりに構成を使用してオブジェクトを定義するほうが有利です)、単にそのクラスでsuperを明示的に定義し、PrimaryParentから継承しないでください。

すべてのクラスでsuperを定義しなければならないという考えは、PrimaryParentを使用すると、明らかに継承ベースのエイリアスであるsuperを使用して、クラス定義行ではなくデータが送信されるクラス本体。

それは私だけかもしれません。

もちろん、すべての状況は異なりますが、使用するオプションを決定する際に私が言ったこれらのことを考慮してください。

1
Kevin

まれかどうかはわかりませんが、確かに同じことをしました。

指摘されているように、言語自体のこの部分を作成することの難しさは、クラスが多重継承を利用する場合です。

1
Matt Dillard

私は時々これを使用します。基本クラスの型を数回入力していることに気付いたら、それをあなたの型定義に似たtypedefに置き換えます。

私はそれが良い使用になると思う。あなたが言うように、基本クラスがテンプレートの場合、入力を節約できます。また、テンプレートクラスは、テンプレートの動作方法のポリシーとして機能する引数を取る場合があります。ベースのインターフェイスが互換性を維持している限り、すべての参照を修正することなく、ベースタイプを自由に変更できます。

Typedefを介した使用は既に十分だと思います。多重継承は多くの基本クラスが存在することを意味するため、とにかく言語にどのように組み込まれるのかわかりません。したがって、論理的に最も重要な基本クラスであると感じるクラスに合うようにtypedefできます。

1
Scott Langham

__superキーワードを使用します。ただし、Microsoft固有のものです。

http://msdn.Microsoft.com/en-us/library/94dw1w7x.aspx

0
Mark Ingram

これは、typedefの代わりにマクロを使用する方法です。これは物事を行うC++の方法ではないことを知っていますが、階層の一番下にある基本クラスのみが継承されたオフセットに作用している場合、継承を通じてイテレータを連結すると便利です。

例えば:

// some header.h

#define CLASS some_iterator
#define SUPER_CLASS some_const_iterator
#define SUPER static_cast<SUPER_CLASS&>(*this)

template<typename T>
class CLASS : SUPER_CLASS {
   typedef CLASS<T> class_type;

   class_type& operator++();
};

template<typename T>
typename CLASS<T>::class_type CLASS<T>::operator++(
   int)
{
   class_type copy = *this;

   // Macro
   ++SUPER;

   // vs

   // Typedef
   // super::operator++();

   return copy;
}

#undef CLASS
#undef SUPER_CLASS
#undef SUPER

私が使用する一般的なセットアップにより、コードが重複しているが、戻り値の型が現在のクラスと一致する必要があるため、オーバーライドする必要がある継承ツリー間での読み取りとコピー/貼り付けが非常に簡単になります。

小文字のsuperを使用してJavaに見られる動作を再現することもできますが、私のコーディングスタイルはマクロにすべて大文字を使用することです。

0
Zhro