222

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

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 是超级常见/罕见/从未见过的吗?
  • 这种使用 typedef 是否超级好(即您是否看到了不使用它的强烈或不那么强烈的理由)?
  • “超级”应该是一件好事,它是否应该在 C++ 中有所标准化,或者通过 typedef 使用它已经足够了吗?

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

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

4

19 回答 19

173

Bjarne Stroustrup 在 C++ 的设计和演进中super提到,在C++ 第一次标准化时,ISO C++ 标准委员会考虑将其作为关键字。

Dag Bruck 提出了这个扩展,称基类为“继承的”。该提案提到了多重继承问题,并且会标记出模棱两可的用途。甚至 Stroustrup 也深信不疑。

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

Michael Tiemann 迟到了,然后证明 typedef'ed super 可以正常工作,使用的技术与本文中提到的相同。

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

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

于 2008-10-07T22:00:31.997 回答
112

我一直使用“继承”而不是超级。(可能是由于 Delphi 背景),并且我总是将其设为私有,以避免在类中错误地省略“继承”但子类尝试使用它时出现问题。

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

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

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

于 2008-10-07T22:00:36.537 回答
39

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

于 2008-10-07T21:59:03.797 回答
21

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

于 2008-10-07T22:51:55.153 回答
15

Super(或继承)是非常好的事情,因为如果您需要在 Base 和 Derived 之间添加另一个继承层,您只需更改两件事:1.“类 Base: foo”和 2. typedef

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

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

于 2008-10-07T22:00:46.397 回答
14

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

于 2008-10-07T21:54:17.877 回答
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它不是私有的,我试图通过选择一个应该提醒其他开发人员的类型名称来防止粗心的使用。

于 2011-04-11T14:22:32.767 回答
10

我已经在许多代码库中看到过这个习惯用法,而且我很确定我什至在 Boost 的库中的某个地方看到过它。但是,据我所知,最常见的名称是base(or Base) 而不是super.

如果使用类模板,这个习惯用法特别有用。例如,考虑以下类(来自真实项目):

template <typename TText, typename TSpec>
class Finder<Index<TText, PizzaChili<TSpec>>, MyFinderType>
    : public Finder<Index<TText, MyFinderImpl<TSpec>>, Default>
{
    using TBase = Finder<Index<TText, MyFinderImpl<TSpec>>, Default>;
    // …
}

继承链使用类型参数来实现编译时多态性。不幸的是,这些模板的嵌套级别非常高。因此,完整类型名称的有意义的缩写对于可读性和可维护性至关重要。

于 2008-10-08T09:17:16.953 回答
4

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

于 2008-10-07T21:52:59.143 回答
4

在您使用的代码中,这种使用 typedef 是超级常见/罕见/从未见过的吗?

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

这种使用 typedef 是否超级好(即您是否看到了不使用它的强烈或不那么强烈的理由)?

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

“超级”应该是一件好事,它是否应该在 C++ 中有所标准化,或者通过 typedef 使用它已经足够了吗?

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

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

于 2008-10-07T21:59:53.670 回答
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”或类似的东西。

--杰夫克++

于 2008-10-07T22:22:43.247 回答
2

在当天从 Turbo Pascal 迁移到 C++ 之后,我曾经这样做是为了获得 Turbo Pascal“继承”关键字的等效项,它的工作方式相同。然而,在用 C++ 编程了几年之后,我停止了它。我发现我只是不太需要它。

于 2008-10-07T21:56:49.867 回答
2

我试图解决这个完全相同的问题;我提出了一些想法,例如使用可变参数模板和包扩展以允许任意数量的父母,但我意识到这将导致像“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.

intopublic继承背后的原因是尽管在它们之间有一个帮助类,但可以完全控制继承。BaseClassPrimaryParentMyObjectBaseClass

这确实意味着您想要拥有的每个类都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我来说不是很有吸引力,使用PrimaryParentallow for super,显然是一个基于继承的别名,留在类定义行而不是数据应该去的类主体中。

那可能只是我。

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

于 2015-07-24T19:29:54.523 回答
1

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

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

于 2008-10-07T21:55:39.513 回答
1

我不时使用它。就在我发现自己多次键入基类类型时,我将用与您的类似的 typedef 替换它。

我认为它可以是一个很好的用途。正如你所说,如果你的基类是一个模板,它可以节省打字。此外,模板类可以接受作为模板应该如何工作的策略的参数。只要基础接口保持兼容,您就可以自由更改基础类型,而无需修复对它的所有引用。

我认为通过 typedef 使用已经足够了。无论如何,我看不出它是如何内置到语言中的,因为多重继承意味着可以有许多基类,所以你可以在你认为适合你逻辑上认为是最重要基类的类时对它进行 typedef。

于 2008-10-07T21:57:45.887 回答
1

除了提供带有注释的代码之外,我不会说太多,这表明 super 并不意味着调用 base!

super != base.

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

  1. super 表示,调用方法的最后一个实现者(不是基方法)
  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. 多态调用:向下调用
  2. super call:向上调用

在里面,main()我们使用了 super 向上调用的多态调用 downards,在现实生活中并不是很有用,但它展示了不同之处。

于 2020-05-05T17:53:33.637 回答
0

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

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

于 2008-10-08T09:12:20.863 回答
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 中的行为,但我的编码风格是对宏使用所有大写字母。

于 2016-02-15T23:18:08.030 回答
0

为什么 c++ 不支持“super”关键字的简单答案是。

DDD(Deadly Diamond of Death)问题。

在多重继承中。编译器会混淆哪个是超类。

在此处输入图像描述

那么哪个超类是“D”的超类?“Both”不能成为解决方案,因为“super”关键字是指针。

于 2021-12-28T01:33:03.600 回答