关于编码风格:在C ++中使用“super”

Using “super” in C++

我的编码风格包括以下习惯用法:

1
2
3
4
5
6
7
8
class Derived : public Base
{
   public :
      typedef Base super; // note that it could be hidden in
                          // protected/private section, instead

      // Etc.
} ;

这使我能够使用"super"作为基别名,例如,在构造函数中:

1
2
3
4
Derived(int i, int j)
   : super(i), J(j)
{
}

甚至当从重写版本内的基类调用方法时:

1
2
3
4
5
6
void Derived::foo()
{
   super::foo() ;

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

它甚至可以被锁住(不过,我仍然需要找到它的用途):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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为verbose和/或templated时。

事实上,超级是用Java实现的,在C语言中实现的(在这里,它被称为"BASE",除非我错了)。但是C++缺少这个关键字。

所以,我的问题是:

  • 使用typedef的代码是否非常常见/罕见/从未见过?
  • typedef super ok的用法是否正确(即,您是否认为不使用它的原因很强烈)?
  • "超级"是否应该是一个好东西,它在C++中是否有一定的标准化,或者通过TyPulf已经足够了?

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

编辑2:现在,在大量使用"super"几个月后,我完全同意Roddy的观点:"super"应该是私有的。我会对他的回答投两次赞成票,但我想我不能。


Bjarne Stroustrup在C++的设计和演化中提到EDOCX1 0作为一个关键词被ISO C++标准委员会考虑,第一次C++是标准化的。

DagBruck提出了这个扩展,称基类为"继承的"。这个建议提到了多重继承问题,并且会标记出不明确的用途。甚至斯特劳斯特鲁普也深信不疑。

经过讨论,DagBruck(是的,提出建议的同一个人)写道,该建议是可实施的,技术上是健全的,没有重大缺陷,并且处理了多重继承。另一方面,没有足够的资金支持,委员会应该处理一个更棘手的问题。

迈克尔·蒂曼迟到了,然后展示了一个typedef'ed super可以很好地工作,使用的技术与本文中提到的相同。

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

如果你没有一个副本,设计和发展是非常值得的封面价格。用过的复印件大约10美元。


我总是用"继承"而不是"超级"。(可能是由于delphi的背景),我总是将其设为私有的,以避免在类中错误地省略"继承"而子类试图使用它时出现问题。

1
2
3
4
5
class MyClass : public MyBase
{
private:  // Prevents erroneous use by other classes.
  typedef MyBase inherited;
...

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

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


这方面的一个问题是,如果忘记(重新)为派生类定义super,那么对super::的任何调用都将编译良好,但可能不会调用所需的函数。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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设置为私有而不是公共或受保护可以消除此问题。)


fwiw-microsoft已经在其编译器中添加了对super的扩展。


超级(或继承)是非常好的事情,因为如果您需要在基和派生之间插入另一个继承层,您只需要更改两件事情:1。"班级基础:foo"和2。Type

如果我记得正确的话,C++标准委员会正在考虑为此添加关键字。直到迈克尔·蒂曼指出这种typedef技巧奏效。

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


我刚刚找到了另一个解决办法。我对今天咬我的typedef方法有一个大问题:

  • typedef需要类名的精确副本。如果有人更改类名但不更改typedef,那么您将遇到问题。

所以我用一个非常简单的模板提出了一个更好的解决方案。

1
2
3
4
5
template <class C>
struct MakeAlias : C
{
    typedef C BaseAlias;
};

所以现在,而不是

1
2
3
4
5
class Derived : public Base
{
private:
    typedef Base Super;
};

你有

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

在这种情况下,BaseAlias不是私有的,我试图通过选择一个类型名来防止不小心的使用,该类型名应该提醒其他开发人员。


我不记得以前看过这个,但乍一看我喜欢。正如Ferroccio所指出的,它在面对MI时并不好用,但是MI更是一个例外,而不是规则,而且没有任何东西表明需要在任何地方都可用才能发挥作用。


我在很多代码中都看到过这个习语,我很肯定在Boost的库中也看到过。然而,据我所知,最常见的名字是EDOCX1(或base),而不是super

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

1
2
3
4
5
6
7
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;
    // …
}

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


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


使用typedef的代码是否非常常见/罕见/从未见过?

我从来没有在C++代码中看到过这样的模式,但这并不意味着它不在那里。

typedef super ok的用法是否正确(即,您是否认为不使用它的原因很强烈)?

它不允许多重继承(不管怎样,完全不允许)。

"超级"是否应该是一个好东西,它在C++中是否有一定的标准化,或者通过TyPulf已经足够了?

由于以上引用的原因(多重继承),没有。您在列出的其他语言中看到"super"的原因是它们只支持单一继承,所以"super"指的是什么并不混淆。当然,在这些语言中,它是有用的,但它在C++数据模型中没有真正的地位。

和FIY:C++/CLI支持这个概念的形式为"超我超级"关键字。但是请注意,C++/CLI也不支持多重继承。


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

例如:

1
2
3
4
5
6
7
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"或类似的东西。

——杰弗克+


在一天内从Turbo Pascal迁移到C++之后,我就这样做了,以便对Turbo Pascal的"继承"关键字有一个等价的词,它的工作方式是相同的。然而,在C++编程了几年后,我停止了它。我发现我只是不太需要它。


我试图解决这个完全相同的问题;我提出了一些想法,例如使用可变模板和包扩展来允许任意数量的父代,但我意识到这将导致类似"super0"和"super1"的实现。我把它弄坏了,因为那几乎比不从一开始就有用。

我的解决方案涉及一个助手类PrimaryParent,它是这样实现的:

1
2
3
4
5
6
7
8
9
template<typename BaseClass>
class PrimaryParent : virtual public BaseClass
{
protected:
    using super = BaseClass;
public:
    template<typename ...ArgTypes>
    PrimaryParent<BaseClass>(ArgTypes... args) : BaseClass(args...){}
}

那么,您想要使用的任何类都将声明为:

1
2
3
4
5
class MyObject : public PrimaryParent<SomeBaseClass>
{
public:
    MyObject() : PrimaryParent<SomeBaseClass>(SomeParams) {}
}

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

public继承BaseClassPrimaryParent的原因是让MyObject完全控制BaseClass的继承,尽管它们之间有一个辅助类。

这意味着你想要拥有super的每个类都必须使用PrimaryParent辅助类,并且每个孩子只能从使用PrimaryParent的一个类继承(因此得名)。

此方法的另一个限制是,MyObject只能继承一个从PrimaryParent继承的类,并且必须使用PrimaryParent继承该类。我的意思是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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>{}

在你放弃这个选择之前,因为似乎有很多限制,而且每一个继承之间都有一个中产阶级,这些事情并不坏。

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

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

那可能就是我。

当然,每种情况都是不同的,但在决定使用哪种选择时,请考虑一下我说过的这些话。


我经常用这个。就在我发现自己多次输入基类类型时,我将用一个类似于您的typedef替换它。

我认为这是一个很好的用途。如您所说,如果您的基类是一个模板,它可以保存输入。此外,模板类可以使用参数作为模板应如何工作的策略。只要基的接口保持兼容,就可以自由地更改基类型,而不必修复对它的所有引用。

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


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

正如已经指出的,使语言本身成为这一部分的困难在于类使用多个继承。


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

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


这是我使用的方法,它使用宏而不是typedef。我知道这不是C++的做事方式,但是当继承者链接到继承时,当只有最底层的层次结构对继承的偏移进行作用时,这是很方便的。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 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

我使用的通用设置使得在继承树之间读取和复制/粘贴非常容易,继承树具有重复的代码,但必须重写,因为返回类型必须与当前类匹配。

我们可以使用一个小写的EDCOX1 10来复制在爪哇中看到的行为,但是我的编码风格是使用宏的所有大写字母。