为什么C ++中没有基类?


84

快速提问:从设计的角度来看,为什么在C ++中没有所有的基础类,object而其他语言通常是什么?


27
实际上,这更多是一个哲学问题,而不是设计问题。简短的答案是“因为Bjarne显然不想这样做”。
杰里·科芬

4
也没有什么可以阻止您创建一个。
GWW

22
我个人认为,所有内容都源自同一基类是设计缺陷。它促进了一种编程风格,这种编程风格会导致更多的问题,而不是值得的(因为您倾向于拥有通用的对象容器,然后需要使用实际的对象进行保护(我不称呼它是糟糕的设计恕我直言))。我是否真的想要一个可以容纳汽车和命令自动化对象的容器?
马丁·约克

3
@Martin可以这样看:例如,直到C ++ 0x auto为止,您都必须对迭代器使用一英里长的类型定义,或者一次使用typedefs。对于更通用的类层次结构,您可以使用objectiterator
slezica 2011年

9
@圣地亚哥:使用统一类型系统几乎总是意味着您最终会得到大量依赖于多态性,动态调度和RTTI的代码,所有这些代码相对昂贵,并且所有这些都阻碍了优化的实现,而这些优化可能在它们不使用。
James McNellis 2011年

Answers:


116

最终裁定可在Stroustrup的FAQ中找到。简而言之,它不传达任何语义含义。这将需要费用。模板对于容器更有用。

为什么C ++没有通用类Object?

  • 我们不需要一个:通用编程在大多数情况下提供了静态类型安全的替代方案。其他情况使用多重继承处理。

  • 没有有用的通用类:真正的通用不带有其自身的语义。

  • “通用”类鼓励对类型和接口的草率思考,并导致过多的运行时检查。

  • 使用通用基类意味着成本:必须对对象进行堆分配以使其具有多态性。这意味着内存和访问成本。堆对象自然不支持复制语义。堆对象不支持简单的作用域行为(这会使资源管理变得复杂)。通用基类鼓励使用dynamic_cast和其他运行时检查。


83
我们不需要/基类对象/我们不需要/思想控制...;)
Piskvor在2011年

3
@Piskvor-大声笑我想看音乐录影带!
拜伦·惠特洛克

10
Objects must be heap-allocated to be polymorphic-我认为这句话通常是不正确的。您绝对可以在堆栈上创建一个多态类的实例,并将其作为指向其基类之一的指针传递,从而产生多态行为。
拜伦

5
在Java中,试图蒙上了参考TO-Foo为基准,TO-Bar时,Bar不继承Foo确定性引发异常。在C ++中,尝试将“指向Foo的指针”转换为“指向Bar的指针”可能会使机器人及时返回以杀死Sarah Connor。
supercat

3
@supercat这就是为什么我们使用dynamic_cast <>的原因。为了避免天网和神秘出现的切斯特菲尔德沙发。
长颈鹿队长

44

首先让我们考虑一下为什么首先要具有基类。我可以想到一些不同的原因:

  1. 支持适用于任何类型的对象的通用操作或集合。
  2. 包括所有对象共有的各种过程(例如内存管理)。
  3. 一切都是对象(没有基元!)。有些语言(例如Objective-C)没有此功能,这使事情变得很混乱。

这是Smalltalk,Ruby和Objective-C品牌的语言具有基类的两个很好的理由(从技术上讲,Objective-C并不真正具有基类,但从所有意图和目的来看,它都有基类)。

对于#1,通过在C ++中包含模板避免了统一一个接口下所有对象的基类的需要。例如:

void somethingGeneric(Base);

Derived object;
somethingGeneric(object);

当您可以通过参数多态性完全保持类型完整性时,这是不必要的!

template <class T>
void somethingGeneric(T);

Derived object;
somethingGeneric(object);

对于#2,在Objective-C中,内存管理过程是类实现的一部分,并且是从基类继承的,而C ++中的内存管理是使用组合而不是继承来执行的。例如,您可以定义一个智能指针包装器,该包装器将对任何类型的对象执行引用计数:

template <class T>
struct refcounted
{
  refcounted(T* object) : _object(object), _count(0) {}

  T* operator->() { return _object; }
  operator T*() { return _object; }

  void retain() { ++_count; }

  void release()
  {
    if (--_count == 0) { delete _object; }
  }

  private:
    T* _object;
    int _count;
};

然后,您将在其包装器中调用方法,而不是在对象本身上调用方法。这不仅允许进行更通用的编程:还使您可以分开关注(因为理想情况下,对象应更关注对象应该做什么,而不是在不同情况下应如何管理其内存)。

最后,在一种既具有原语又具有诸如C ++之类的实际对象的语言中,拥有基类(对于每个)中,失去值都有),因为那样的话,您将拥有某些不符合该接口的值。为了在这种情况下使用原语,您需要将它们提升到对象中(如果编译器不会自动执行)。这造成很多复杂性。

因此,对于您的问题的简短答案是:C ++没有基类,因为通过模板具有参数多态性,它不需要。


没关系!很高兴您发现这很有帮助:)
Jonathan Sterling

2
对于第3点,我反对C#。C#有原语可以被装箱为objectSystem.Object),但他们并不需要的人。编译器,int以及System.Int32是别名和可以互换使用; 运行时在需要时处理装箱。
科尔·约翰逊

在C ++中,不需要装箱。使用模板,编译器会在编译时生成正确的代码。
Rob K

2
请注意,尽管这个(旧的)答案演示了一个引用计数的智能指针,但现在std::shared_ptr应该使用C ++ 11代替。

15

C ++变量的主要范例是按值传递,而不是按引用传递。强制一切都从根派生Object将使按值传递它们成为事实错误。

(因为接受按值作为参数的对象,将按定义对其进行切片并删除其灵魂)。

这是不受欢迎的。C ++让您考虑是想要值还是引用语义,从而给您选择的余地。这是性能计算中的一件大事。


1
它本身不会是一个错误。类型层次结构已经存在,并且在适当的时候可以很好地利用值传递。但是,这会鼓励采用更多基于堆的策略,在这种情况下,按引用传递更合适。
丹尼斯·齐克福斯

恕我直言,一种良好的语言应具有以下不同的继承类型:在这种情况下,派生类可以通过逐条引用合法地用作基类对象,而在可以通过逐条值将其转换为基类对象的情况下,以及那些都不合法的地方。对于可以切片的事物具有通用基本类型的价值将受到限制,但是许多事物无法切片,因此必须间接存储。拥有这些东西来自普通人的额外成本Object相对较小。
超级猫

6

问题是C ++中有这种类型!是的void。:-)任何指针都可以安全地隐式转换为void *,包括指向基本类型,没有虚拟表的类和具有虚拟表的类的指针。

由于它应该与所有这些类别的对象兼容,因此它void本身不能包含虚拟方法。如果没有虚拟函数和RTTI,则无法从类型中获取有用的信息void(它与EVERY类型匹配,因此只能说出对每种类型都适用的事实),但是虚拟函数和RTTI会使简单类型变得非常无效,并且会阻止C ++成为现实。适用于具有直接内存访问等功能的低级编程的语言。

因此,有这种类型。由于语言的低级性质,它仅提供了非常简约的接口(实际上是空的)。:-)


指针类型和对象类型不同。您不能具有类型的对象void
凯文·潘科

1
实际上,这就是C ++中继承通常的工作方式。它所做的最重要的事情之一就是指针和引用类型的兼容性:在需要指向类的指针的情况下,可以给出指向派生类的指针。在两种类型之间进行static_cast指针的能力是它们在层次结构中连接的标志。从这个角度来看,void绝对是C ++中的通用基类。
Ellioh

如果声明一个类型的变量,void它将不会编译,并且会出现此错误。在Java中,您可以有一个类型变量,Object它可以工作。那是void和“真实”类型之间的区别。也许这是所有void内容的基本类型,但是由于它不提供任何构造函数,方法或字段,因此无法判断它是否存在。此断言无法得到证明或证明。
凯文·潘科

1
在Java中,每个变量都是一个引用。就是这样。:-)(顺便说一句,C ++中也有一些抽象类,您不能使用它们来声明变量)。在我的回答中,我解释了为什么不可能从无效中获取RTTI。因此,从理论上讲,无效仍然是一切的基类。static_cast是一个证明,因为它仅在相关类型之间执行强制转换,并且可以用于void。但是您是对的,无效的RTTI访问的确与高级语言的基本类型有很大的不同。
Ellioh

1
@Ellioh在咨询了C ++ 11标准§3.9.1p9之后,我承认这void是一种类型(并发现了巧妙的用法? :),但距离成为通用基类还有很长的路要走。一方面,它是“无法完成的不完整类型”,另一方面,它是没有void&类型的。
bcrist 2015年

-2

C ++是一种强类型的语言。但是令人困惑的是,在模板专业化的情况下,它没有通用的对象类型。

以模式为例

template <class T> class Hook;
template <class ReturnType, class ... ArgTypes>
class Hook<ReturnType (ArgTypes...)>
{
   ...
   ReturnType operator () (ArgTypes... args) { ... }
};

可以实例化为

Hook<decltype(some_function)> ...;

现在,假设我们希望为特定功能使用相同的功能。喜欢

template <auto fallback> class Hook;
template <auto fallback, class ReturnType, class ... ArgTypes>
class Hook<ReturnType fallback(ArgTypes...)>
{
   ...
   ReturnType operator () (ArgTypes... args) { ... }
};

用专门的实例化

Hook<some_function> ...

但是可惜的是,即使在专业化之前T类可以代表任何类型(无论是否具有类),也没有任何等效项auto fallback(在这种情况下,我使用该语法作为最明显的通用非类型)。专业化之前的非类型模板参数。

因此,通常这种模式不会从类型模板参数转换为非类型模板参数。

就像在C ++语言中遇到很多麻烦一样,答案很可能是“没有委员会成员想到它”。


即使(类似)您ReturnType fallback(ArgTypes...)可以使用,也可能是错误的设计。template <class T, auto fallback> class Hook; template <class ReturnType, class ... ArgTypes> class Hook<ReturnType (ArgTypes...), ReturnType(*fallback)(ArgTypes...)> ...做您想要的并且意味着其模板参数Hook是一致的种类
Caleth

-4

C ++最初被称为“带有类的C”。它是C语言的一种进步,与其他一些更现代的事物(例如C#)不同。而且您不能将C ++视为一种语言,而是将其视为语言的基础(是的,我还记得Scott Meyers撰写的《有效C ++》)。

C本身是语言,C编程语言及其预处理器的混合。

C ++增加了另一种组合:

  • 类/对象方法

  • 范本

  • STL

我个人不喜欢直接从C到C ++的某些东西。枚举功能就是一个例子。C#允许开发人员使用它的方式更好:它将枚举限制在其自己的范围内,它具有Count属性,并且很容易迭代。

由于C ++希望与C兼容,因此设计师允许C语言将其整体输入C ++(有些细微的差异,但是我不记得使用C编译器可以做的任何事情,无法使用C ++编译器)。


“不能将C ++视作一种语言,而是视作一种语言的基础”。。。实际上要解释和说明这种奇怪的表述在做什么吗?多种范例不同于“多种语言”,尽管可以说预处理器与其余编译器步骤是脱节的-很好。枚举-如果需要,可以将它们轻而易举地包装在类范围中,并且整个语言都缺乏内省-一个主要问题,尽管在这种情况下可以近似使用-请参见Boost vault的benum代码。您的观点仅是问:C ++不是通用基础开始的吗?
托尼·德罗伊

@Tony:奇怪的是,我提到的书中他们甚至没有提到的另一种语言是预处理器,这是您单独考虑的唯一一本书。我理解您的观点,因为预处理器首先解析其自己的输入。但是,拥有模板机制就像拥有另一种为您进行概括的语言一样。应用模板语法,编译器将为每种类型生成函数。当您可以用C编写程序并将其输入提供给C ++编译器时,这是两种语言的一种:C和带有object => C ++的C
sergiol 2011年

STL可以看作是附加组件,但是它具有非常实用的类和容器,可以通过模板自然地扩展C ++的功能。我说的枚举是NATIVE枚举。在谈到Boost时,您所依赖的不是该语言的NATIVE第三方代码。在C#中,我不必包含任何可迭代且自作用域枚举的外部元素。在C ++中,我经常使用的一个技巧是调用枚举的最后一项-不分配值,因此它们会自动从零开始并递增-类似于NUM_ITEMS,这给了我上限。
sergiol 2011年

2
感谢您的进一步解释-我至少可以看到您来自哪里;我听到一些人抱怨说,学习使用模板与其他C ++编程脱节,尽管那当然不是我的经验。我将让其他读者根据您提出的其他观点做出自己的选择。干杯。
Tony Delroy

1
我认为拥有通用基本类型的最大问题是,如果没有任何虚拟方法,那么这种类型将毫无用处。C ++不想为没有虚拟方法的结构类型增加任何开销,但是具有虚拟方法的任何类型的每个对象至少都必须具有指向调度表的指针。如果类和结构居住在不同的Universe中,则可能有一个通用的类对象,但是类和结构在C ++中基本上是同一件事。
2013年
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.