在C ++中使用“super”

时间:2008-10-07 21:49:43

标签: c++ coding-style

我的编码风格包括以下习语:

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

      // Etc.
} ;

这使我能够使用“super”作为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
}

无论如何,我发现使用“typedef super”非常有用,例如,当Base是详细和/或模板时。

事实上,super是用Java实现的,也是用C#实现的(除非我错了,否则称为“base”)。但是C ++缺少这个关键字。

所以,我的问题:

  • 是使用typedef super common / rare /在您使用的代码中从未见过吗?
  • 是使用typedef super Ok(即你是否看到强烈或不那么强烈的理由不使用它)?
  • 应该“超级”是一件好事,它应该在C ++中有些标准化,还是已经足够使用typedef?

编辑: Roddy提到了typedef应该是私有的。这意味着任何派生类都无法在不重新声明的情况下使用它。但我想这也会阻止super :: super chaining(但是谁会为此而哭?)。

编辑2:现在,在大量使用“超级”之后几个月,我全心全意地同意罗迪的观点:“超级”应该是私人的。我会两次回答他的答案,但我想我不能。

18 个答案:

答案 0 :(得分:138)

Bjarne Stroustrup在 C ++的设计和演变中提到,{C}标准委员会首次将super作为关键词时考虑了C ++的标准化。

Dag Bruck提出了这个扩展,调用基类“继承”。该提案提到了多重继承问题,并且会标记含糊不清的用法。甚至Stroustrup也深信不疑。

经过讨论,Dag Bruck(是的,提出提案的人)写道,该提案是可实现的,技术上合理,没有重大缺陷,并处理多重继承。另一方面,没有足够的支持,委员会应该处理棘手的问题。

迈克尔·蒂尔曼迟到了,然后证明了一个typedef'ed super工作得很好,使用了与这篇文章中提到的相同的技术。

所以,不,这可能永远不会标准化。

如果您没有副本,设计和演变非常值得支付价格。使用过的副本大约需要10美元。

答案 1 :(得分:93)

我总是使用“继承”而不是超级。 (可能是由于Delphi的背景),我总是把它设为 private ,以避免在类中错误地省略'inherited'但子类试图使用它时的问题。

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

我用于创建新类的标准“代码模板”包括typedef,因此我几乎没有机会忽略它。

我认为链接的“超级超级”建议并不是一个好主意 - 如果你这样做,你可能很难与特定的层次结构联系起来,改变它可能会严重破坏它

答案 2 :(得分:33)

这样做的一个问题是,如果你忘记(重新)为派生类定义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设为私有而不是公开或受保护来消除这个问题。)

答案 3 :(得分:17)

FWIW Microsoft已在其编译器中添加了__super的扩展名。

答案 4 :(得分:14)

超级(或继承)是非常好的事情,因为如果你需要在Base和Derived之间粘贴另一个继承层,你只需要改变两件事:1。“class Base:foo”和2. typedef < / p>

如果我没记错的话,C ++标准委员会正在考虑为此添加一个关键字......直到Michael Tiemann指出这种类型的def技巧有效。

至于多重继承,因为它受程序员控制,你可以做任何你想做的事:可能是super1和super2,或者其他什么。

答案 5 :(得分:12)

我刚刚找到了另一种解决方法。我今天咬了我的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不是私密的,我试图通过选择应该提醒其他开发人员的类型名称来防止粗心使用。

答案 6 :(得分:11)

我不记得以前见过这个,但乍一看我喜欢它。正如Ferruccio所指出的那样,它在面对MI时效果不佳,但是MI比规则更为例外,没有任何东西可以说在任何地方都需要有用的东西才能有用。

答案 7 :(得分:8)

我已经在许多代码中看到了这个习惯用法,我很确定我甚至在Boost的库中看到过它。但是,据我记得,最常见的名称是base(或Base),而不是super

如果使用模板类,这个习惯用法特别有用。例如,请考虑以下类(来自real project):

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 :(得分:3)

是否使用了typedef super common / rare /在您使用的代码中从未见过?

我从未在我使用的C ++代码中看到过这种特殊模式,但这并不意味着它不在那里。

是使用typedef super Ok(即你是否看到强烈或不那么强烈的理由不使用它)?

它不允许多重继承(干净利落,无论如何)。

应该“超级”是一件好事,它应该在C ++中有些标准化,还是已经足够使用typedef?

由于上述原因(多重继承),没有。你在其他语言中看到“超级”的原因是它们只支持单继承,因此对于“超级”所指的内容并不存在混淆。当然,在这些语言中它很有用,但它在C ++数据模型中并没有真正的位置。

哦,以及FYI:C ++ / CLI以“__super”关键字的形式支持这个概念。但请注意,C ++ / CLI也不支持多重继承。

答案 9 :(得分:3)

当基础是复杂的模板类型时,我经常看到它被使用,有时作为super_t使用(例如boost::iterator_adaptor这样做)

答案 10 :(得分:2)

在当天从Turbo Pascal迁移到C ++之后,我曾经这样做以获得Turbo Pascal“inherited”关键字的等价物,它的工作方式相同。但是,在使用C ++编程几年后,我停止了这样做。我发现我并不需要它。

答案 11 :(得分:2)

在超类中使用typedef的另一个原因是在对象的继承中使用复杂模板。

例如:

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 ++

答案 12 :(得分:1)

我经常使用它。就在我发现自己输入基类类型几次时,我会用类似于你的typedef替换它。

我认为它可以很好用。如你所说,如果您的基类是模板,它可以节省输入。此外,模板类可以采用充当模板应该如何工作的策略的参数。只要基座的接口保持兼容,您就可以自由更改基本类型,而无需修复所有对它的引用。

我认为通过typedef的使用已经足够了。我无法看到它是如何构建到语言中的,因为多重继承意味着可以有许多基类,所以你可以根据你认为适合你逻辑上认为最重要的基类的类来定义它。

答案 13 :(得分:1)

我不知道这种情况是否罕见,但我确实做了同样的事情。

正如已经指出的那样,使用语言本身的这一部分的困难是当一个类使用多重继承时。

答案 14 :(得分: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) {}
}

为了避免在PrimaryParent BaseClass上使用虚拟继承,需要使用可变数量参数的构造函数来构造BaseClass

public BaseClass继承到PrimaryParent后面的原因是让MyObject完全控制BaseClass的继承,尽管有一个帮手他们之间的课程。

这意味着您希望拥有super的每个类都必须使用PrimaryParent帮助程序类,并且每个子类只能使用PrimaryParent从一个类继承(因此名称)。

此方法的另一个限制是,MyObject只能继承一个继承自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>{}

在你放弃这个作为一个选项之前,由于看似数量的限制以及每个继承之间都有一个中间人类,所以这些事情都不错。

多重继承是一个强大的工具,但在大多数情况下,只有一个主要父级,如果有其他父级,它们可能是Mixin类,或者不继承自PrimaryParent的类无论如何。如果仍然需要多继承(尽管许多情况会使用组合来定义对象而不是继承),而不仅仅是在该类中明确定义super而不是从PrimaryParent继承。

必须在每个类中定义super的想法对我来说并不是很吸引人,使用PrimaryParent允许super显然是一个基于继承的别名,留在类定义中行而不是数据应该去的类主体。

那可能只是我。

当然,每种情况都不同,但在决定使用哪种选项时,请考虑我所说的这些事情。

答案 15 :(得分:1)

除了现在的代码外,我不会说太多其他注释,这些注释表明super并不意味着要调用base!

super != base.

简而言之,“超级”到底是什么意思?那么“基数”应该是什么意思?

  1. super表示调用方法(不是基本方法)的最后一个实现者
  2. base意味着选择哪个类是多重继承中的默认基础。

这2条规则适用于classdef类型。

考虑图书馆实施者和图书馆用户,谁是超级用户,谁是基础用户?

有关更多信息,这里是将代码粘贴粘贴到您的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. 多态通话:向下通话
  2. 超级通话:向上通话

main()内部,我们使用了向上调用超级的多态调用项,在现实生活中并没有真正的用处,但它证明了两者之间的区别。

答案 16 :(得分:0)

我使用__super关键字。但它是微软特有的:

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

答案 17 :(得分: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中的行为,但我的编码风格是使用宏的所有大写字母。