Por que o `void * 'não é implicitamente convertido em C ++?

30

Em C, não há necessidade de converter a void *em qualquer outro tipo de ponteiro, pois ele é sempre promovido com segurança. No entanto, em C ++, este não é o caso. Por exemplo,

int *a = malloc(sizeof(int));

funciona em C, mas não em C ++. (Observação: eu sei que você não deve usar mallocem C ++ ou, nesse caso new, e preferir ponteiros inteligentes e / ou o STL; isso é solicitado apenas por curiosidade) Por que o padrão C ++ não permite essa conversão implícita, enquanto o padrão C faz?

wolfPack88
fonte
3
long *a = malloc(sizeof(int));Ops, alguém esqueceu de alterar apenas um tipo!
Doval
4
@Doval: Isso ainda é facilmente corrigido usando-o sizeof(*a).
wolfPack88
3
Acredito que o argumento de @ ratchetfreak é que a razão C faz essa conversão implícita é porque mallocnão é possível retornar um ponteiro para o tipo alocado. newé C ++ retorna um ponteiro para o tipo alocado, portanto, o código C ++ escrito corretamente nunca terá nenhum void *s para transmitir.
Gort the Robot
3
Como um aparte, não é outro tipo de ponteiro. Somente ponteiros de dados precisam ser aplicados.
Deduplicator
3
@ wolfPack88 você tem seu histórico errado. C ++ teve void, C fez não . Quando essa palavra-chave / ideia foi adicionada a C, ela foi alterada para se adequar às necessidades de C. Isso foi logo após tipos de ponteiro começou a ser verificado em tudo . Veja se você pode encontrar on-line o panfleto de descrição da K&R C ou uma cópia vintage de um texto de programação em C, como o C Primer do Waite Group . O ANSI C estava cheio, com recursos suportados ou inspirados em C ++, e o K&R C era muito mais simples. Portanto, é mais correto que o C ++ estendeu o C como existia na época e o C que você conhece foi retirado do C ++.
JDługosz 9/03/2015

Respostas:

39

Como as conversões implícitas de tipo geralmente não são seguras, e o C ++ adota uma postura mais segura quanto à digitação do que C.

C geralmente permitirá conversões implícitas, mesmo que a maioria das chances seja de que a conversão seja um erro. Isso ocorre porque C assume que o programador sabe exatamente o que está fazendo e, se não, é problema do programador, não do compilador.

O C ++ geralmente não permite coisas que possam ser erros e exige que você declare explicitamente sua intenção com uma conversão de tipo. Isso ocorre porque o C ++ está tentando ser amigável ao programador.

Você pode perguntar como é amigável quando na verdade exige que você digite mais.

Bem, veja bem, qualquer linha de código, em qualquer programa, em qualquer linguagem de programação, geralmente será lida muito mais vezes do que será escrita (*). Portanto, a facilidade de leitura é muito mais importante do que a facilidade de escrever. E, ao ler, destacar conversões potencialmente inseguras por meio de projeções explícitas de tipos ajuda a entender o que está acontecendo e a ter um certo nível de certeza de que o que está acontecendo é, de fato, o que deveria acontecer.

Além disso, o inconveniente de digitar a conversão explícita é trivial em comparação com o inconveniente de horas e horas de solução de problemas para encontrar um bug causado por uma atribuição incorreta da qual você poderia ter sido avisado, mas nunca foi.

(*) Idealmente, ele será escrito apenas uma vez, mas será lido sempre que alguém precisar revisá-lo para determinar sua adequação à reutilização, e sempre que houver solução de problemas em andamento e sempre que alguém precisar adicionar código próximo a ele, e sempre que houver solução de problemas de código próximo e assim por diante. Isso é verdade em todos os casos, exceto nos scripts "escreva uma vez, execute e jogue fora" e, portanto, não é de admirar que a maioria das linguagens de script possua uma sintaxe que facilite a facilidade de escrever com total desconsideração da facilidade de leitura. Já pensou que perl é completamente incompreensível? Você não está sozinho. Pense em idiomas como "somente gravação".

Mike Nakis
fonte
C é essencialmente um passo acima do código da máquina. Eu posso perdoar C por coisas assim.
Qix
8
Programas (qualquer que seja o idioma) devem ser lidos. Operações explícitas se destacam.
Matthieu M.
10
Vale ressaltar que as transmissões void*são mais inseguras em C ++, porque com a maneira como certos recursos de OOP são implementados em C ++, o ponteiro para o mesmo objeto pode ter um valor diferente, dependendo do tipo de ponteiro.
Hyde
@MatthieuM. muito verdadeiro. Obrigado por adicionar isso, vale a pena fazer parte da resposta.
Mike Nakis
11
@ MatthieuM .: Ah, mas você realmente não quer ter que fazer tudo explicitamente. A legibilidade não é aprimorada por ter mais para ler. Embora neste ponto o equilíbrio seja claramente por ser explícito.
Deduplicator
28

Aqui está o que Stroustrup diz :

Em C, você pode converter implicitamente um nulo * em um T *. Isso não é seguro

Ele então continua mostrando um exemplo de como o vácuo * pode ser perigoso e diz:

... Consequentemente, em C ++, para obter um T * de um void *, você precisa de uma conversão explícita. ...

Por fim, ele observa:

Um dos usos mais comuns dessa conversão insegura em C é atribuir o resultado de malloc () a um ponteiro adequado. Por exemplo:

int * p = malloc (sizeof (int));

No C ++, use o operador new typesafe:

int * p = novo int;

Ele explica muito mais detalhes sobre isso em The Design and Evolution of C ++ .

Portanto, a resposta se resume a: O criador do idioma acredita que é um padrão inseguro, tornando-o ilegal e fornecendo maneiras alternativas de realizar para que o padrão normalmente era usado.

Gort the Robot
fonte
11
Em relação ao mallocexemplo, vale a pena notar que malloc(obviamente) não chamará um construtor; portanto, se for um tipo de classe para o qual você está alocando memória, ser capaz de converter implicitamente no tipo de classe seria enganoso.
usar o seguinte comando
Esta é uma resposta muito boa, sem dúvida melhor que a minha. Não gosto apenas da abordagem "argumento da autoridade".
Mike Nakis
"new int" - uau, como um novato em C ++, eu estava tendo problemas para pensar em uma maneira de adicionar um tipo de base à pilha, e nem sabia que você poderia fazer isso. -1 uso para malloc.
Katana314
3
@MikeNakisFWIW, minha intenção era complementar sua resposta. Nesse caso, a pergunta era "por que eles fizeram isso", então achei que a justificativa da audiência do designer-chefe é justificada.
Gort the Robot
11

Em C, não há necessidade de converter um nulo * em qualquer outro tipo de ponteiro, ele é sempre promovido com segurança.

É sempre promovido, sim, mas dificilmente seguro .

C ++ desabilita esse comportamento precisamente porque tenta ter um sistema de tipo mais seguro que C, e esse comportamento não é seguro.


Considere, em geral, estas três abordagens para a conversão de tipos:

  1. forçar o usuário a escrever tudo, para que todas as conversões sejam explícitas
  2. suponha que o usuário saiba o que está fazendo e converta qualquer tipo para qualquer outro
  3. implementar um sistema de tipos completo com genéricos seguros para tipos (modelos, novas expressões), operadores de conversão definidos pelo usuário e forçar conversões explícitas apenas das coisas que o compilador não pode ver como implicitamente seguras

Bem, eu sou feio e um obstáculo prático para fazer qualquer coisa, mas pode realmente ser usado onde é necessário muito cuidado. C aproximadamente optou por 2, que é mais fácil de implementar, e C ++ por 3, que é mais difícil de implementar, mas mais seguro.

Sem utilidade
fonte
Foi um longo caminho difícil para chegar ao 3, com exceções adicionadas mais tarde e modelos mais tarde ainda.
JDługosz 9/03/2015
Bem, os modelos não são realmente necessários para um sistema de tipos seguro tão completo: as linguagens baseadas em Hindley-Milner se dão muito bem sem eles, sem conversões implícitas e sem necessidade de escrever os tipos explicitamente também. (Claro, línguas dependem do tipo de apagamento / coleta de lixo polimorfismo / alto-kinded, coisas que C ++ em vez evita.)
leftaroundabout
1

Por definição, um ponteiro nulo pode apontar para qualquer coisa. Qualquer ponteiro pode ser convertido em um ponteiro nulo e, portanto, você poderá converter novamente chegando exatamente ao mesmo valor. No entanto, ponteiros para outros tipos podem ter restrições, como restrições de alinhamento. Por exemplo, imagine uma arquitetura em que os caracteres possam ocupar qualquer endereço de memória, mas os números inteiros devem começar em limites de endereço pares. Em certas arquiteturas, ponteiros inteiros podem até contar 16, 32 ou 64 bits por vez, para que um caractere * possa realmente ter um múltiplo do valor numérico de int * enquanto aponta para o mesmo local na memória. Nesse caso, a conversão de um vazio * na verdade arredondaria os bits que não podem ser recuperados e, portanto, não são reversíveis.

Simplificando, o ponteiro nulo pode apontar para qualquer coisa, incluindo coisas que outros ponteiros podem não ser capazes de apontar. Assim, a conversão para o ponteiro nulo é segura, mas não o contrário.

roserez
fonte