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

从设计的角度来看,为什么在 C + + 中没有最基本的基类,在其他语言中 object通常是什么?

11486 次浏览

The definitive ruling is found in Stroustrup's FAQs. In short, it doesn't convey any semantic meaning. It will have a cost. Templates are more useful for containers.

Why doesn't C++ have a universal class Object?

  • We don't need one: generic programming provides statically type safe alternatives in most cases. Other cases are handled using multiple inheritance.

  • There is no useful universal class: a truly universal carries no semantics of its own.

  • A "universal" class encourages sloppy thinking about types and interfaces and leads to excess run-time checking.

  • Using a universal base class implies cost: Objects must be heap-allocated to be polymorphic; that implies memory and access cost. Heap objects don't naturally support copy semantics. Heap objects don't support simple scoped behavior (which complicates resource management). A universal base class encourages use of dynamic_cast and other run-time checking.

The dominant paradigm for C++ variables is pass-by-value, not pass-by-ref. Forcing everything to be derived from a root Object would make passing them by value an error ipse facto.

(Because accepting an Object by value as parameter, would by definition slice it and remove its soul).

This is unwelcome. C++ makes you think about whether you wanted value or reference semantics, giving you the choice. This is a big thing in performance computing.

Let's first think about why you'd want to have a base-class in the first place. I can think of a few different reasons:

  1. To support generic operations or collections that will work on objects of any type.
  2. To include various procedures which are common to all objects (such as memory management).
  3. Everything is an object (no primitives!). Some languages (like Objective-C) don't have this, which makes things pretty messy.

These are the two good reasons that languages of the Smalltalk, Ruby and Objective-C brand have base-classes (technically, Objective-C doesn't really have a base-class, but for all intents and purposes, it does).

For #1, the need for a base-class that unifies all objects under a single interface is obviated by the inclusion of templates in C++. For instance:

void somethingGeneric(Base);


Derived object;
somethingGeneric(object);

is unnecessary, when you can maintain type integrity all the way through by means of parametric polymorphism!

template <class T>
void somethingGeneric(T);


Derived object;
somethingGeneric(object);

For #2, whereas in Objective-C, memory management procedures are part of a class's implementation, and are inherited from the base class, memory management in C++ is performed using composition rather than inheritance. For instance, you can define a smart pointer wrapper which will perform reference counting on objects of any type:

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;
};

Then, instead of calling methods on the object itself, you'd be calling methods in its wrapper. This not only allows more generic programming: it also lets you separate concerns (since ideally, your object should be more concerned about what it should do, than how its memory should be managed in different situations).

Lastly, in a language that has both primitives and actual objects like C++, the benefits of having a base-class (a consistent interface for every value) are lost, since then you have certain values which cannot conform to that interface. In order to use primitives in that sort of a situation, you need to lift them into objects (if your compiler won't do it automatically). This creates a lot of complication.

So, the short answer to your question: C++ doesn't have a base-class because, having parametric polymorphism through templates, it doesn't need to.

C++ was initially called "C with classes". It is a progression of the C language, unlike some other more modern things like C#. And you can not see C++ as a language, but as a foundation of languages (Yes, I am remembering the Scott Meyers book Effective C++).

C itself is a mix of languages, the C programming language and its preprocessor.

C++ adds another mix:

  • the class/objects approach

  • templates

  • the STL

I personally don't like some stuff that come directly from C to C++. One example is the enum feature. The way C# allows the developer to use it is way better: it limits the enum in its own scope, it has a Count property and it is easily iterable.

As C++ wanted to be retrocompatible with C, the designer was very permissive to allow the C language to enter in its whole to C++ (there are some subtle differences, but I do not remember any thing that you could do using a C compiler that you could not do using a C++ compiler).

The problem is that there IS such a type in C++! It is void. :-) Any pointer may be safely implicitly cast to void *, including pointers to basic types, classes with no virtual table and classes with virtual table.

Since it should be compatible with all those categories of objects, void itself can not contain virtual methods. Without virtual functions and RTTI, no useful information on type can be obtained from void (it matches EVERY type, so can tell only things that are true for EVERY type), but virtual functions and RTTI would make simple types very ineffective and stop C++ from being a language suitable for low-level programming with direct memory access etc.

So, there is such type. It just provides very minimalistic (in fact, empty) interface due to low-level nature of the language. :-)

C++ is a strongly typed language. Yet it is puzzling that it does not have a universal object type in the context of template specialization.

Take, for example, the pattern

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

which can be instantiated as

Hook<decltype(some_function)> ...;

Now let's assume that we want the same for a particular function. Like

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

with the specialized instantiation

Hook<some_function> ...

But alas, even though class T can stand in for any type (class or not) before specialization, there is no equivalent auto fallback (I am using that syntax as the most obvious generic non-type one in this context) that could stand in for any non-type template argument before specialization.

So in general this pattern does not transfer from type template arguments to non-type template arguments.

Like with a lot of corners in the C++ language, the answer is likely "no committee member thought of it".