在C ++中使用“ super”


203

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

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#中实现的,除非我错了,否则在C#中将其称为“基础”。但是C ++缺少此关键字。

所以,我的问题是:

  • 您使用的代码中是否经常使用typedef super common / rare / never?
  • 使用typedef super可以吗(即您是否出于强烈的理由不使用它)?
  • “ super”应该是一件好事吗,应该在C ++中进行某种标准化,还是通过typedef进行使用已经足够了?

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

编辑2:现在,在大规模使用“超级”几个月后,我完全同意罗迪的观点:“超级”应该是私有的。我会两次投票赞成他的回答,但我想我不能。


太棒了!正是我想要的。直到现在为止,我都不需要使用这种技术。我的跨平台代码的绝佳解决方案。
阿兰克利

6
对我来说,这super看起来Java不错,但是...但是C++支持多重继承。
ST3 2013年

2
@ user2623967:对。在简单继承的情况下,一个“超级”就足够了。现在,如果您具有多个继承,则拥有“ superA”,“ superB”等是一个很好的解决方案:您想从一个实现或另一个实现中调用该方法,因此必须告诉您想要的实现。使用MyFirstBase<MyString, MyStruct<MyData, MyValue>>
类似

请注意,从模板继承时,引用模板时不需要包括模板参数。例如: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
嗯,更正 这似乎工作任何C ++标准,不只是14
Thanasis Papoutsidakis

Answers:


151

Bjarne的Stroustrup的提到了设计和++的C进化super一个关键字是由ISO C ++标准委员会第一次C ++进行了标准化考虑。

达格·布鲁克(Dag Bruck)提出了这一扩展,称其“继承”了基类。该提案提到了多重继承问题,并且将标记出模棱两可的用法。甚至Stroustrup也被说服了。

在讨论之后,达格·布鲁克(Dag Bruck)(是的,由同一人提出提案)写道,该提案是可实施的,技术上合理的,没有重大缺陷,并处理了多个继承。另一方面,没有足够的资金来承担责任,委员会应该处理一个棘手的问题。

迈克尔·提曼(Michael Tiemann)迟到了,然后证明了使用使用typedef的super可以很好地工作,使用的技巧与本文中要求的相同。

因此,不,这可能永远不会标准化。

如果您没有副本,则Design and Evolution非常物有所值。使用过的副本大约需要10美元。


5
D&E确实是一本好书。但似乎我需要重新阅读-我都不记得任何一个故事。
Michael Burr

2
我记得在D&E中没有讨论的三个功能。这是第一个(在索引中查找“ Michael Tiemann”以查找故事),第二个星期规则是第二个(在索引中查找“两周规则”),第三个被命名为参数(“查找”索引中的“命名实参”)。
Max Lybbert

12
typedef技术存在一个主要缺陷:不尊重DRY。唯一的解决方法是使用丑陋的宏来声明类。继承时,基数可能是长的多参数模板类,甚至更糟。(例如,多类),您将不得不第二次重写所有内容。最后,我看到了带有模板类参数的模板库的一个大问题。在这种情况下,super是模板(而不是模板的实例化)。无法定义。即使在C ++ 11中,您也需要using这种情况。
v.oddou

105

我一直使用“继承”而不是超级。(可能是由于Delphi的背景),我总是做到这一点 private,以避免在类中错误地省略“继承”而子类尝试使用它时的问题。

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

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

我认为链式的“ super :: super”建议不是一个好主意-如果这样做,您可能很难与特定的层次结构绑定在一起,更改它可能会严重破坏东西。


2
至于链接super :: super,正如我在问题中提到的,我仍然需要找到一个有趣的用法。现在,我仅将其视为一种hack,但值得一提的是,如果仅是与Java的区别(您不能链接“ super”)。
paercebal

4
几个月后,我转向了您的观点(由于您提到的“超级”,我DID遇到了一个错误,就像您提到的...)。我猜您的答案很正确,包括链接。^ _ ^ ...
paercebal

我现在如何使用它来调用父类方法?
mLstudent33

这是否意味着我必须声明所有基类方法,virtual如下所示:martinbroadhurst.com/typedef-super.html
mLstudent33

36

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


谢谢你的发言。这种副作用逃脱了我的注意。虽然它可能不会为构造函数使用而编译,但我猜在其他任何地方都存在该错误。
paercebal

5
但是,私有typedef将阻止链式使用,如原始文章所述。
AnT

1
遇到这个确切的错误是导致我提出这个问题的原因:(
Steve Vermeulen 2012年

因此super1用于基本和super2派生?DerivedAgain可以同时使用吗?
mLstudent33,

20

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


这里的一些开发人员开始推动使用__super。起初我回推,因为我觉得那是“错误”和“非标准”。但是,我已经爱上了它。
Aardvark

8
我正在使用Windows应用程序,并且喜欢__super扩展名。令我感到沮丧的是,标准委员会拒绝了此处提到的typedef技巧,因为尽管这种typedef技巧很好,但是当您更改继承层次结构时,它比编译器关键字需要更多的维护,并且可以正确处理多个继承(不需要两个像super1和super2这样的typedef)。简而言之,我同意其他评论者的观点,即MS扩展非常有用,任何专门使用Visual Studio的人都应该强烈考虑使用它。
布赖恩

15

超级(或继承)非常好,因为如果您需要在Base和Derived之间插入另一个继承层,则只需更改两件事:1.“ Base Base:foo”类和2. typedef

如果我没记错的话,C ++标准委员会正在考虑为此添加一个关键字……直到Michael Tiemann指出此typedef技巧起作用为止。

至于多重继承,由于它是在程序员的控制之下,所以您可以做任何您想做的事情:也许是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别名是不利的一面,因为您对罗迪(Roddy)克里斯托弗Kristopher)的答案中提到的错误持开放态度(例如,您可以(错误地)Derived代替得出MakeAlias<Derived>
Alexander Malakhov 2013年

3
您也无权访问初始化程序列表中的基类构造函数。(这可以在C ++ 11中使用继承构造函数MakeAlias或完美转发来补偿,但这确实需要您引用MakeAlias<BaseAlias>构造函数,而不仅仅是直接引用C'的构造函数。)
Adam H. Peterson

12

我不记得以前看到过这种情况,但是乍一看,我喜欢它。正如Ferruccio所指出的那样,面对MI并不能很好地工作,但是MI不仅是规则,更是例外,没有什么可以说某些东西需要在所有地方都有用才能有用。


6
仅支持以下短语:“没有什么可以说某些东西需要在所有地方都可用才能有用。”
Tanktalus

1
同意 它是有益的。我只是在查看boost类型特征库,以查看是否有一种通过模板生成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;
    // …
}

不要介意这些有趣的名字。这里的重点是继承链使用类型参数来实现编译时多态性。不幸的是,这些模板的嵌套级别变得很高。因此,缩写对于可读性和可维护性至关重要。


出于相同的原因,我最近使用了“ super”。公共继承太痛苦了,以至于无法处理其他问题... :-) ...
paercebal


4

您使用的代码中是否经常使用typedef super common / rare / never?

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

使用typedef super可以吗(即您是否出于强烈的理由不使用它)?

它不允许多重继承(无论如何都是干净的)。

“ super”应该是一件好事吗,应该在C ++中进行某种标准化,还是通过typedef进行使用已经足够了?

由于上述原因(多重继承),否。您在列出的其他语言中看到“ super”的原因是它们仅支持单一继承,因此对于“ super”指的是什么没有混淆。当然,在这些语言中,它很有用,但在C ++数据模型中并没有真正的位置。

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


4
作为对策,Perl同时具有多重继承 SUPER。会有一些混淆,但是明确记录了虚拟机用来查找内容的算法。就是说,我很少看到在多个基类提供相同方法的情况下使用MI的情况,但无论如何都可能引起混淆。
Tanktalus

1
Microsoft Visual Studio是否为C ++实现__super关键字。如果只有一个继承的类提供一种具有正确签名的方法(最常见的情况,如Tanktalus所述),则它只会选择唯一有效的选择。如果两个或多个继承的类提供功能匹配,则该功能不起作用,并且要求您明确。
布赖恩

3

对超类使用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 ++


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

public继承BaseClass成in 的原因PrimaryParent是要MyObject完全控制in的继承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无论如何都不继承。如果仍然需要多重继承(尽管在很多情况下使用composition来定义对象而不是继承会更为有益),而不是仅super在该类中进行显式定义并且不要从继承PrimaryParent

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

不过那可能只是我。

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


1

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

super != base.

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

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

这两个规则适用于类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. 多态通话:向下通话
  2. 超级通话:向上通话

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



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中看到的行为,但是我的编码风格是将所有大写字母用于宏。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.