Por que não há classe base em C ++?

84

Do ponto de vista do design, por que, em C ++, não existe uma classe-base mãe de todas, o que normalmente ocorre objectem outras linguagens?

Slezica
fonte
27
Esta é realmente mais uma questão filosófica do que uma questão de design. A resposta curta é "porque Bjarne aparentemente não queria fazer isso."
Jerry Coffin
22
Pessoalmente, acho que é uma falha de design ter tudo derivado da mesma classe base. Ele promete um estilo de programação que causa mais problemas do que vale a pena (já que você tende a ter contêineres genéricos de objetos dos quais você precisa inventariar para usar o objeto real (isso eu desaprovo o nome de IMHO de design ruim)). Eu realmente quero um contêiner que possa abrigar carros e objetos de automação de comando?
Martin York
3
@Martin, mas olhe desta forma: por exemplo, até C ++ 0x auto, você tinha que usar definições de tipo de quilômetros de extensão para iteradores, ou typedefs únicos. Com uma hierarquia de classes mais genérica, você pode apenas usar objectou iterator.
slezica
9
@Santiago: O uso de um sistema de tipo unificado quase sempre significa que você acaba com uma grande quantidade de código que depende de polimorfismo, despacho dinâmico e RTTI, todos os quais são relativamente caros e todos inibem otimizações que são possíveis quando eles não são usados.
James McNellis
2
@GWW - ... além do fato de que não se aplica a nenhum dos contêineres STL.
Chris Lutz de

Respostas:

116

A decisão definitiva pode ser encontrada nas Perguntas frequentes da Stroustrup . Em suma, não transmite nenhum significado semântico. Terá um custo. Os modelos são mais úteis para contêineres.

Por que C ++ não tem um objeto de classe universal?

  • Não precisamos de um: a programação genérica fornece alternativas estaticamente seguras na maioria dos casos. Outros casos são tratados com herança múltipla.

  • Não existe uma classe universal útil: um verdadeiramente universal não carrega semântica própria.

  • Uma classe "universal" incentiva o pensamento desleixado sobre tipos e interfaces e leva a uma verificação excessiva do tempo de execução.

  • Usar uma classe base universal implica em custo: os objetos devem ser alocados no heap para serem polimórficos; isso implica custo de memória e acesso. Objetos heap não suportam naturalmente a semântica de cópia. Objetos heap não suportam comportamento de escopo simples (o que complica o gerenciamento de recursos). Uma classe base universal incentiva o uso de dynamic_cast e outras verificações de tempo de execução.

Capitão girafa
fonte
83
Não precisamos de / objeto de classe base / não precisamos de / controle de pensamento ...;)
Piskvor deixou o prédio em
3
@Piskvor - LOL Eu quero ver o videoclipe!
Byron Whitlock de
11
Objects must be heap-allocated to be polymorphic- Eu não acho que esta afirmação é geralmente correta. Você pode definitivamente criar uma instância de uma classe polimórfica na pilha e passá-la como um ponteiro para uma de suas classes base, produzindo um comportamento polimórfico.
Byron
5
Em Java, a tentativa de converter uma referência Fooa uma referência a Barquando Barnão herda Foo, deterministicamente , lança uma exceção. Em C ++, a tentativa de lançar um ponteiro para Foo em um ponteiro para Barra poderia enviar um robô de volta no tempo para matar Sarah Connor.
supercat de
3
@supercat É por isso que usamos dynamic_cast <>. Para evitar SkyNet e sofás Chesterfield que aparecem misteriosamente.
Capitão Girafa
44

Vamos primeiro pensar sobre por que você gostaria de ter uma classe base em primeiro lugar. Posso pensar em alguns motivos diferentes:

  1. Para dar suporte a operações genéricas ou coleções que funcionarão em objetos de qualquer tipo.
  2. Para incluir vários procedimentos que são comuns a todos os objetos (como gerenciamento de memória).
  3. Tudo é um objeto (sem primitivos!). Algumas linguagens (como Objective-C) não têm isso, o que torna as coisas muito confusas.

Estas são as duas boas razões pelas quais as linguagens da marca Smalltalk, Ruby e Objective-C têm classes base (tecnicamente, Objective-C não tem realmente uma classe base, mas para todos os efeitos e propósitos, ela tem).

Para o nº 1, a necessidade de uma classe base que unifique todos os objetos em uma única interface é evitada pela inclusão de modelos em C ++. Por exemplo:

void somethingGeneric(Base);

Derived object;
somethingGeneric(object);

é desnecessário, quando você pode manter a integridade do tipo por meio de polimorfismo paramétrico!

template <class T>
void somethingGeneric(T);

Derived object;
somethingGeneric(object);

Para o nº 2, enquanto em Objective-C, os procedimentos de gerenciamento de memória são parte da implementação de uma classe e são herdados da classe base, o gerenciamento de memória em C ++ é executado usando composição em vez de herança. Por exemplo, você pode definir um wrapper de ponteiro inteligente que fará a contagem de referência em objetos de qualquer tipo:

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

Então, em vez de chamar métodos no próprio objeto, você chamaria métodos em seu invólucro. Isso não apenas permite uma programação mais genérica: também permite separar interesses (uma vez que, idealmente, seu objeto deveria estar mais preocupado com o que deveria fazer, do que como sua memória deveria ser gerenciada em diferentes situações).

Por último, em uma linguagem que possui objetos primitivos e reais como C ++, os benefícios de ter uma classe base (uma interface consistente para todos os valores) são perdidos, já que você tem certos valores que não podem estar em conformidade com essa interface. Para usar primitivos nesse tipo de situação, você precisa transformá-los em objetos (se o seu compilador não fizer isso automaticamente). Isso cria muitas complicações.

Então, uma resposta curta para sua pergunta: C ++ não tem uma classe base porque, tendo polimorfismo paramétrico por meio de templates, não precisa.

Jonathan Sterling
fonte
Está certo! Que bom que você achou útil :)
Jonathan Sterling
2
Para o ponto 3, contraponho com C #. C # tem primitivas que podem ser encaixotadas em object( System.Object), mas não precisam ser. Para o compilador, inte System.Int32são apelidos e podem ser usados ​​alternadamente; o tempo de execução lida com boxe quando necessário.
Cole Johnson
Em C ++, não há necessidade de boxe. Com os modelos, o compilador gera o código correto no momento da compilação.
Rob K
2
Observe que, embora esta resposta (antiga) demonstre um ponteiro inteligente contado por referência, o C ++ 11 agora tem o std::shared_ptrque deve ser usado.
15

O paradigma dominante para variáveis ​​C ++ é a passagem por valor, não a passagem por ref. Forçar tudo a ser derivado de uma raiz Objecttornaria a passagem deles por valor um erro ipse facto.

(Porque aceitar um objeto por valor como parâmetro, por definição iria cortá-lo e remover sua alma).

Isso não é bem-vindo. C ++ faz você pensar se você deseja valor ou semântica de referência, dando-lhe a escolha. Isso é importante na computação de desempenho.

ver
fonte
1
Não seria um erro por si só. Já existem hierarquias de tipo e utilizam muito bem a passagem por valor quando apropriado. No entanto, encorajaria mais estratégias baseadas em heap onde a passagem por referência é mais apropriada.
Dennis Zickefoose
IMHO, uma boa linguagem deve ter tipos distintos de herança para as situações em que uma classe derivada pode ser usada legitimamente como um objeto de classe base usando fatia por referência, aqueles em que pode ser transformada em uma usando fatia por valor, e aqueles em que nenhum dos dois é legítimo. O valor de ter um tipo de base comum para coisas que podem ser fatiadas seria limitado, mas muitas coisas não podem ser fatiadas e devem ser armazenadas indiretamente; o custo adicional de derivar essas coisas de um comum Objectseria relativamente pequeno.
supercat
5

O problema é que EXISTE esse tipo em C ++! É sim void. :-) Qualquer ponteiro pode ser lançado implicitamente com segurança void *, incluindo ponteiros para tipos básicos, classes sem tabela virtual e classes com tabela virtual.

Uma vez que deve ser compatível com todas essas categorias de objetos, voidele mesmo não pode conter métodos virtuais. Sem funções virtuais e RTTI, nenhuma informação útil sobre o tipo pode ser obtida void(ele corresponde a TODOS os tipos, então pode dizer apenas coisas que são verdadeiras para TODOS os tipos), mas as funções virtuais e RTTI tornariam os tipos simples muito ineficazes e impediriam o C ++ de ser uma linguagem adequada para programação de baixo nível com acesso direto à memória, etc.

Então, existe esse tipo. Ele apenas fornece uma interface muito minimalista (na verdade, vazia) devido à natureza de baixo nível da linguagem. :-)

Ellioh
fonte
Os tipos de ponteiro e tipos de objeto não são iguais. Você não pode ter um objeto do tipo void.
Kevin Panko
1
Na verdade, é assim que normalmente funciona a herança em C ++. Uma das coisas mais importantes que ele faz é a compatibilidade dos tipos de ponteiro e referência: onde um ponteiro para uma classe é necessário, um ponteiro para uma classe derivada pode ser fornecido. E a capacidade de static_cast ponteiros entre dois tipos é um sinal de que eles estão conectados em uma hierarquia. Desse ponto de vista, void definitivamente é uma classe base universal em C ++.
Ellioh
Se você declarar uma variável do tipo, voidela não será compilada e você obterá este erro . Em Java, você poderia ter uma variável do tipo Objecte ela funcionaria. Essa é a diferença entre voidum tipo "real". Talvez seja verdade que voidé o tipo base para tudo, mas como não fornece nenhum construtor, método ou campo, não há como saber se ele está lá ou não. Esta afirmação não pode ser provada ou refutada.
Kevin Panko
1
Em Java, cada variável é uma referência. Essa é a diferença. :-) (BTW, em C ++ também existem classes abstratas que você não pode usar para declarar uma variável). E na minha resposta expliquei porque não é possível obter RTTI do vazio. Portanto, teoricamente vazio ainda é uma classe base para tudo. static_cast é uma prova, uma vez que realiza casts apenas entre tipos relacionados e pode ser usado para void. Mas você está certo, a ausência de acesso RTTI no void realmente o torna muito diferente dos tipos básicos nas linguagens de nível superior.
Ellioh
1
@Ellioh Após consultar o padrão C ++ 11 §3.9.1p9, admito que voidé um tipo (e descobri um uso? : um tanto inteligente de ), mas ainda está muito longe de ser uma classe base universal. Por um lado, é "um tipo incompleto que não pode ser completado" e, por outro, não existe nenhum void&tipo.
bcrist
-2

C ++ é uma linguagem fortemente tipada. No entanto, é intrigante que ele não tenha um tipo de objeto universal no contexto da especialização de modelo.

Pegue, por exemplo, o padrão

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

que pode ser instanciado como

Hook<decltype(some_function)> ...;

Agora, vamos supor que queremos o mesmo para uma função específica. Gostar

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

com a instanciação especializada

Hook<some_function> ...

Mas, infelizmente, embora a classe T possa representar qualquer tipo (classe ou não) antes da especialização, não há equivalente auto fallback(estou usando essa sintaxe como o não-tipo genérico mais óbvio neste contexto) que poderia substituir qualquer argumento de modelo sem tipo antes da especialização.

Portanto, em geral, esse padrão não é transferido de argumentos de template de tipo para argumentos de template não-tipo.

Como acontece com muitos cantos na linguagem C ++, a resposta provavelmente é "nenhum membro do comitê pensou nisso".

user7339377
fonte
Mesmo se (algo como) o seu ReturnType fallback(ArgTypes...)funcionasse, seria um projeto ruim. template <class T, auto fallback> class Hook; template <class ReturnType, class ... ArgTypes> class Hook<ReturnType (ArgTypes...), ReturnType(*fallback)(ArgTypes...)> ...faz o que você quer e significa que os parâmetros do modelo Hooksão de tipos
Caleth,
-4

C ++ foi inicialmente chamado de "C com classes". É uma progressão da linguagem C, ao contrário de outras coisas mais modernas como C #. E você não pode ver C ++ como uma linguagem, mas como uma base de linguagens (Sim, estou me lembrando do livro de Scott Meyers Effective C ++).

O próprio C é uma mistura de linguagens, a linguagem de programação C e seu pré-processador.

C ++ adiciona outra combinação:

  • a abordagem de classe / objetos

  • modelos

  • o STL

Eu pessoalmente não gosto de algumas coisas que vêm diretamente de C para C ++. Um exemplo é o recurso enum. A maneira como o C # permite que o desenvolvedor o use é muito melhor: ele limita o enum em seu próprio escopo, tem uma propriedade Count e é facilmente iterável.

Como C ++ queria ser retrocompatível com C, o designer foi muito permissivo em permitir que a linguagem C entrasse em sua totalidade para C ++ (existem algumas diferenças sutis, mas não me lembro de nada que você pudesse fazer usando um compilador C que você não poderia fazer usando um compilador C ++).

sergiol
fonte
"não posso ver C ++ como uma linguagem, mas como uma base de linguagens" ... se importaria em explicar e ilustrar aonde essa declaração bizarra quer chegar? Paradigmas múltiplos são distintos de "linguagens múltiplas", embora seja justo dizer que o pré-processador é separado do resto das etapas do compilador - bom argumento. Re enums - eles podem ser trivialmente agrupados em um escopo de classe se desejado, e toda a linguagem carece de introspecção - um grande problema, embora neste caso possa ser aproximado - consulte o código benum do Boost vault. Seu ponto é apenas Q: C ++ não começou com uma base universal?
Tony Delroy
@Tony: Curiosamente, o livro a que me referi a única coisa que eles nem mencionam como outra linguagem é o pré-processador, que é o único que você considera separadamente. Eu entendo seu ponto de vista, já que o pré-processador analisa sua própria entrada primeiro. Mas, ter um mecanismo de modelos é como ter outra linguagem que faz a generalização para você. Você aplica uma sintaxe de modelo e terá o compilador gerando funções para cada tipo que você possui. Quando você pode ter um programa escrito em C e fornecer sua entrada para um compilador C ++, são duas linguagens em uma: C e C com objetos => C ++
sergiol
STL, pode ser visto como um add-on, mas possui classes e containers muito práticos que através de templates estendem NATIVAMENTE o poder do C ++. E os enums que eu estava dizendo são os enums NATIVOS. Quando você fala do Boost, está contando com um código de terceiros que não é NATIVO para o idioma. Em C #, não preciso incluir nada externo para ter um enum iterável e com escopo próprio. Em C ++, um truque que costumo usar é chamar o último item de um enum - sem valores atribuídos, então eles são automaticamente iniciados do zero e incrementados - algo como NUM_ITEMS, e isso me dá o limite superior.
sergiol
2
obrigado por sua explicação adicional - eu posso pelo menos ver de onde você está vindo; Eu ouvi algumas pessoas reclamarem que aprender a usar modelos parecia separado de outra programação C ++, embora essa certamente não seja minha experiência. Vou deixar outros leitores fazerem o que quiserem das outras perspectivas que você apresentar. Felicidades.
Tony Delroy de
1
Acho que o maior problema em ter um tipo de base universal é que esse tipo seria inútil se não tivesse nenhum método virtual; C ++ não queria adicionar nenhuma sobrecarga para tipos de estrutura que não tinham nenhum método virtual, mas todo objeto de qualquer tipo que tivesse qualquer método virtual deve ter, no mínimo, um ponteiro para uma tabela de despacho. Se classes e estruturas habitavam universos diferentes, poderia ser possível ter um objeto de classe universal, mas classes e estruturas são basicamente a mesma coisa em C ++.
supercat