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


203

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

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

      // Etc.
} ;

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

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はJavaとC#で実装されています(私が間違っていない限り、「ベース」と呼ばれます)。しかし、C ++にはこのキーワードがありません。

だから、私の質問:

  • typedefのこの使用は、あなたが使用するコードで見られる超一般的/まれ/決してありませんか?
  • これはtypedef super Okの使用ですか(つまり、使用しないという強い理由またはそれほど強くない理由がわかりますか)?
  • 「スーパー」は良いものである必要がありますか、それはC ++でいくらか標準化されている必要がありますか、それともtypedefによるこの使用はすでに十分ですか?

編集:ロディは、typedefはプライベートであるべきだと述べました。これは、派生クラスは再宣言しないと使用できないことを意味します。しかし、私はそれがsuper :: superチェーニングも防ぐと思います(しかし、誰がそれを叫ぶのでしょうか?)。

編集2:さて、「スーパー」を大量に使用してから数か月後、私はロディの見解に全面的に同意します。「スーパー」はプライベートである必要があります。私は彼の答えを2回賛成するでしょうが、私はできないと思います。


驚くばかり!まさに私が探していたもの。今までこのテクニックを使う必要があったとは思わないでください。私のクロスプラットフォームコードの優れたソリューション。
AlanKley、2008年

6
私にとっては、何の問題もないsuperように見えますJavaが... C++多重継承をサポートしています。
ST3 2013

2
@ user2623967:そうです。単純な継承の場合、「スーパー」は1つで十分です。ここで、多重継承がある場合、「superA」、「superB」などを持つことは良い解決策です。ある実装から別の実装からメソッドを呼び出したいので、必要な実装を通知する必要があります。「スーパー」のようなtypedefを使用すると、(たとえば、MyFirstBase<MyString, MyStruct<MyData, MyValue>>どこにでも書く代わりに)簡単にアクセス/書き込み可能な名前を提供できます
paercebal

テンプレートから継承する場合、テンプレートを参照するときにテンプレート引数を含める必要がないことに注意してください。例:template <class baz> struct Foo {...void bar() {...} ...}; struct Foo2: Foo<AnnoyinglyLongListOfArguments> { void bar2() { ... Foo::bar(); ...} };これは、gcc 9.1 --std = c ++ 1y(c ++ 14)で私に働きました。
Thanasis Papoutsidakis 2014

1
...ええと、訂正。14だけでなく、どのc ++標準でも機能するようです
Thanasis Papoutsidakis '13

回答:


151

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

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

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

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

したがって、いいえ、これはおそらく標準化されません。

コピーがない場合は、デザインと進化はカバー価格の価値があります。使用済みコピーは約10ドルで入手できます。


5
D&Eは確かに良い本です。でも、もう一度読む必要があるようです。どちらの話も覚えていません。
マイケルバー

2
D&Eで議論され、受け入れられなかった3つの機能を覚えています。これは最初のもの(記事を見つけるためにインデックスで "Michael Tiemann"を検索)、2週間のルールは2番目(インデックスで "2週間のルール"を検索)、3番目はパラメーター(名前を検索)インデックスの名前付き引数」)。
Max Lybbert 2008年

12
このtypedefテクニックには大きな欠陥があります。DRYを尊重していません。唯一の回避策は、醜いマクロを使用してクラスを宣言することです。継承する場合、ベースは長いマルチパラメータテンプレートクラスか、それより悪い場合があります。(例えば、マルチクラス)そのすべてをもう一度書き直す必要があります。最後に、テンプレートクラスの引数を持つテンプレートベースで大きな問題が発生します。この場合、スーパーはテンプレートです(テンプレートのインスタンスではありません)。これはtypedefできません。C ++ 11でも、usingこの場合は必要です。
v.oddou 2015年

105

私は常にスーパーではなく「継承」を使用してきました。(おそらくDelphiの背景が原因です)、「継承」がクラスから誤って省略されたがサブクラスがそれを使用しようとした場合の問題を回避するために、常に私はそれをプライベートにします。

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

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

連鎖された「super :: super」の提案は良い考えではないと思います。そうしている場合、おそらく特定の階層に非常に強く結びついており、それを変更すると、物事がひどく壊れる可能性があります。


2
super :: superのチェーンについては、質問で述べたように、興味深い使い方を見つける必要があります。今のところ、これはハックとしてしか見えませんが、Javaとの違い( "super"をチェインできない場合)についてのみ言及する価値はあります。
paercebal 2008年

4
数か月後、私はあなたの視点に転向しました(そして、あなたが言及したように、忘れられていた「スーパー」のために、私はDIDにバグがありました...)。連鎖を含めて、あなたの答えはまったく正しいと思います。^ _ ^ ...
paercebal 2009年

これを使用して親クラスのメソッドを呼び出すにはどうすればよいですか?
mLstudent33

virtualここに示すように、すべてのBaseクラスメソッドを宣言する必要があるということですか?martinbroadhurst.com/typedef-super.html
mLstudent33

36

これに関する1つの問題は、派生クラスのスーパーを(再)定義することを忘れた場合、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ではなくprivateにすることで解消できます。)


発言ありがとうございます。この副作用は私の注意を免れていた。それはおそらくコンストラクタ使用のためにコンパイルされませんが、他のどこでも、バグはそこにあると思います。
paercebal

5
ただし、private typedefは、元の投稿で言及されている連鎖使用を防止します。
AnT 2009年

1
この正確なバグに遭遇すると、私はこの質問に私を導きました:(
Steve Vermeulen

super1Baseとsuper2Derivedで使用しますか?DerivedAgainは両方を使用できますか?
mLstudent33

20

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


ここで数人の開発者が__superの使用を推進し始めました。「間違っている」と「非標準」だと感じたので、最初は反論しました。しかし、私はそれを愛するようになりました。
Aardvark、

8
私はWindowsアプリに取り組んでおり、__ super拡張機能が大好きです。このtypedefトリックは良いものの、継承階層を変更するときにコンパイラーキーワードよりも多くのメンテナンスが必要であり、複数の継承を正しく処理するため(2つを必要とせずに)、標準委員会がここで述べたtypedefトリックを支持して標準委員会が拒否したのは悲しいことです。 super1やsuper2などのtypedef)。要するに、MS拡張機能は非常に有用であり、Visual Studioだけを使用している人は、それを使用することを強く検討する必要があるという他のコメントに同意します。
Brian

15

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

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

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


13

別の回避策を見つけました。私は今日私に噛みついた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はプライベートではなく、他の開発者に警告するタイプ名を選択することにより、不注意な使用から保護しようとしました。


4
publicエイリアスはマイナス面です。ロディクリストファーの回答で言及されているバグを受け入れているためです(たとえば、(誤って)のDerived代わりにから派生できますMakeAlias<Derived>
Alexander Malakhov 2013年

3
また、イニシャライザリストの基本クラスコンストラクタにアクセスすることもできません。(これはで継承コンストラクタを使用してC ++の11を補償することができるMakeAliasか、完璧な転送が、それが参照する必要がないMakeAlias<BaseAlias>コンストラクタだけではなく、に言及C直接コンストラクタさん。)
アダムH.ピーターソン

12

これまでに見た覚えはありませんが、一見すると気に入っています。フェルッチョのノート、それはMIの顔にはうまく動作しませんが、MIは、ルールよりも例外であり、何かのニーズが有用であるためにどこでも使えるように述べている何もありません。


6
「何かがどこでも使えるようになる必要があることを示すものは何もない」というフレーズだけに賛成投票してください。
タンクタルス2008年

1
同意した。それは便利です。テンプレートを介してtypedefを生成する方法があるかどうかを確認するために、ブーストタイプの特性ライブラリを調べていました。残念ながら、それはあなたができるようには思えません。
Ferruccio

9

このイディオムが多くのコードで採用されているのを見てきましたが、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;
    // …
}

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


最近も同じ理由で「スーパー」を使っていました。パブリック
ヘリテージ


4

typedefのこの使用は、あなたが使用するコードで見られる超一般的/まれ/決してありませんか?

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

これはtypedef super Okの使用ですか(つまり、使用しないという強い理由またはそれほど強くない理由がわかりますか)?

多重継承はできません(とにかく、きれいに)。

「スーパー」は良いものである必要がありますか、それはC ++でいくらか標準化されている必要がありますか、それともtypedefによるこの使用はすでに十分ですか?

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

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


4
対置として、Perlには多重継承 SUPERの両方があります。いくつか混乱がありますが、何かを見つけるためにVMが通過するアルゴリズムは明確に文書化されています。とは言っても、複数の基本クラスが同じ方法を提供していて、とにかく混乱が生じる可能性がある場合、MIが使用されることはほとんどありません。
タンクタルス2008年

1
Microsoft Visual Studioは、CLI用かどうかに関係なく、C ++用の__superキーワードを実装しています。継承されたクラスの1つだけが正しい署名のメソッドを提供する場合(Tanktalusによって言及されている最も一般的なケース)、有効な選択肢のみが選択されます。2つ以上の継承されたクラスが関数の一致を提供する場合、それは機能せず、明示的にする必要があります。
ブライアン

3

スーパークラスに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のメンバーを参照したいすべての場所でtypedefが繰り返されてしまいます。

これらの場合、多重継承でも​​機能しますが、「super」という名前のtypedefはなく、「base_A_t」またはそのような名前になります。

--jeffk ++


2

同じ日にTurbo PascalからC ++に移行した後、Turbo Pascalの「継承された」キーワードに相当するものを作成するためにこれを使用しました。しかし、数年間C ++でプログラミングした後、私はそれをやめました。あまり必要ないことがわかりました。


1

珍しいかどうかはわかりませんが、確かに同じことをしています。

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


1

私は時々これを使います。自分が基本クラスのタイプを数回タイプアウトしているのを見つけたとき、私はそれをあなたと同様のtypedefに置き換えます。

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

typedefによる使用ですでに十分だと思います。とにかく、言語にどのように組み込まれるかはわかりません。多重継承は多くの基本クラスが存在する可能性があるため、論理的には最も重要な基本クラスであると感じるクラスに適していると思われる場合は、typedefを使用してそれを定義できるからです。


1

私はこのまったく同じ問題を解決しようとしていました。私は、可変個のテンプレートやパック拡張を使用して任意の数の親を許可するなど、いくつかのアイデアを投げかけましたが、「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ます。

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

これは、必要なすべてのクラスがヘルパークラスをsuper使用する必要があることを意味しPrimaryParent、各子はPrimaryParent(したがって名前を使用して)1つのクラスからのみ継承できます。

このメソッドのもう1つの制限は、MyObjectから継承するクラスを1つだけ継承でき、そのクラスはをPrimaryParent使用して継承する必要があること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つしかありませんPrimaryParent。他の親がいる場合、それらはMixinクラス、またはとにかく継承しないクラスになります。多重継承が依然として必要な場合(多くの状況では、継承の代わりにオブジェクトを定義するためにコンポジションを使用することが有益です)、superそのクラスで明示的に定義し、から継承しないでくださいPrimaryParent

superすべてのクラスで定義する必要があるという考えは私にとってあまり魅力的ではありません。データを送信するクラス本体の代わりにクラス定義行にとどまるPrimaryParentためsuperに、を使用して、明らかに継承ベースのエイリアスを許可します。

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

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


1

スーパーがベースを呼び出すことを意味しないことを示すコメント付きの現在のコードを除いて、私はあまり言いません!

super != base.

要するに、とにかく「スーパー」とはどういう意味ですか?そして、「ベース」とはどういう意味ですか?

  1. スーパーは、メソッドの最後の実装者(基本メソッドではない)を呼び出すことを意味します
  2. baseは、多重継承でデフォルトのベースとなるクラスを選択することを意味します。

この2つの規則は、クラスのtypedefに適用されます。

ライブラリの実装者とライブラリのユーザーを検討してください。誰がスーパーで誰がベースですか。

詳細については、ここにIDEにコピーして貼り付けるための作業コードを示します。

#include <iostream>

// Library defiens 4 classes in typical library class hierarchy
class Abstract
{
public:
    virtual void f() = 0;
};

class LibraryBase1 :
    virtual public Abstract
{
public:
    void f() override
    {
        std::cout << "Base1" << std::endl;
    }
};

class LibraryBase2 :
    virtual public Abstract
{
public:
    void f() override
    {
        std::cout << "Base2" << std::endl;
    }
};

class LibraryDerivate :
    public LibraryBase1,
    public LibraryBase2
{
    // base is meaningfull only for this class,
    // this class decides who is my base in multiple inheritance
private:
    using base = LibraryBase1;

protected:
    // this is super! base is not super but base!
    using super = LibraryDerivate;

public:
    void f() override
    {
        std::cout << "I'm super not my Base" << std::endl;
        std::cout << "Calling my *default* base: " << std::endl;
        base::f();
    }
};

// Library user
struct UserBase :
    public LibraryDerivate
{
protected:
    // NOTE: If user overrides f() he must update who is super, in one class before base!
    using super = UserBase; // this typedef is needed only so that most derived version
    // is called, which calls next super in hierarchy.
    // it's not needed here, just saying how to chain "super" calls if needed

    // NOTE: User can't call base, base is a concept private to each class, super is not.
private:
    using base = LibraryDerivate; // example of typedefing base.

};

struct UserDerived :
    public UserBase
{
    // NOTE: to typedef who is super here we would need to specify full name
    // when calling super method, but in this sample is it's not needed.

    // Good super is called, example of good super is last implementor of f()
    // example of bad super is calling base (but which base??)
    void f() override
    {
        super::f();
    }
};

int main()
{
    UserDerived derived;
    // derived calls super implementation because that's what
    // "super" is supposed to mean! super != base
    derived.f();

    // Yes it work with polymorphism!
    Abstract* pUser = new LibraryDerivate;
    pUser->f();

    Abstract* pUserBase = new UserBase;
    pUserBase->f();
}

ここでのもう1つの重要なポイントは次のとおりです。

  1. ポリモーフィックコール:下方へのコール
  2. スーパーコール:上向きに呼び出します

内部でmain()は、スーパーコールが上向きにコールするポリモーフィックコールダウンワードを使用していますが、実際の生活ではそれほど役に立ちませんが、違いを示しています。



0

これは、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で見られる動作を再現することもできますが、私のコーディングスタイルはマクロにすべて大文字を使用することです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.