Comentários
- isso provavelmente foi feito da mesma maneira que em C simples, consulte Escrever código genérico quando seu destino é um compilador C
- @Telastyn, IIRC, modelos foram uma novidade do CFront 3 (cerca de 1990).
- @Telastyn: por volta do ano 2000, a maioria dos compiladores C ++ existentes não forneciam modelos muito bem (mesmo que fingissem fornecê-los, a maioria deles era muito problemática para uso em produção). O suporte a modelos servia principalmente para suportar contêineres genéricos, mas longe dos requisitos para suportar algo como Alexandrescu ' s " Design C ++ moderno " exemplos.
- Você tem certeza de que a geração do código em tempo de compilação foi usada antes do poder dos modelos? Eu era jovem na época, mas tenho a impressão de que, nos velhos tempos, apenas os tipos mais simples de geração de código em tempo de compilação eram usados. Se você realmente queria escrever um programa para escrever seu programa, você escreveu um programa / script cuja saída era o código-fonte C, em vez de fazer isso com a linguagem de modelo.
- @Joshua – As perguntas são fechadas como duplicadas se as respostas existentes para outra questão abordam as questões primárias por trás da presente questão. " Correspondências " exatas não são necessárias; o objetivo é combinar rapidamente o OP com as respostas às suas perguntas. Em outras palavras, sim, é ' uma duplicata. Dito isso, esta questão cheira a " OMG! Como o mundo existia antes de …?! " que não é ' uma pergunta terrivelmente construtiva. Doc Brown apontou em um comentário anterior (e agora excluído) como esse fenômeno pode afetar os comentários.
Resposta
Além do void *
ponteiro que é coberto na resposta de Robert , uma técnica como esta foi usada (isenção de responsabilidade: Memória de 20 anos):
#define WANTIMP #define TYPE int #include "collection.h" #undef TYPE #define TYPE string #include "collection.h" #undef TYPE int main() { Collection_int lstInt; Collection_string lstString; }
Onde eu esqueci a mágica exata do pré-processador dentro de collection.h
, mas era algo assim:
class Collection_ ## TYPE { public: Collection_ ## TYPE () {} void Add(TYPE value); private: TYPE *list; size_t n; size_t a; } #ifdef WANTIMP void Collection_ ## TYPE ::Add(TYPE value) #endif
Comentários
- +1 – Há um livro de Bjarne Stroustrup onde ele conta a história do C ++ (encontrei-o anos atrás na biblioteca da universidade), e essa técnica foi explicitamente descrita como uma motivação para o desenvolvimento de modelos.
- Essa técnica tem um nome? Talvez " Macros X " ?
Resposta
O A maneira tradicional de implementar genéricos sem ter genéricos (a razão pela qual os modelos foram criados) é usar um ponteiro void.
typedef struct Item{ void* data; } Item; typedef struct Node{ Item Item; struct Node* next; struct Node* previous; } Node;
Neste código de exemplo, uma árvore binária ou uma lista duplamente ligada pode ser representada. Como item
encapsula um ponteiro void, qualquer tipo de dados pode ser armazenado lá. Claro, você teria que saber o tipo de dados em tempo de execução para que possa convertê-los de volta em um objeto utilizável.
Comentários
- Isso funciona para armazenamento genérico, mas e as funções genéricas? @gnat ' s macros podem lidar com isso, mas esta pequena classe horrível pode ' t. Venceu ' isso também levou a um pesadelo de problemas de aliasing estrito durante o processamento dos dados?
- @MadScienceDreams Por que não poderia ' você aplica isso aos endereços de função?
- @IdeaHat: Sim, aplicaria. Mas tudo isso vem de uma época em que havia muito menos ênfase em fazer com que as ferramentas salvassem o programador de seus próprios erros. Em outras palavras, você tinha que ter cuidado porque a linguagem lhe deu muito mais corda para atirar no próprio pé.
- Você poderia saber o tipo de dados na outra extremidade do ponteiro armazenando em algum lugar. .talvez em uma mesa? Talvez uma … vtable? Esse é o tipo de coisa que o compilador abstrai para nós. Em outras palavras, antes que os compiladores lidassem com modelos e polimorfismo, tínhamos que fazer os nossos próprios.
- @IdeaHat: Para funções genéricas, olhe qsort na biblioteca C. Ele pode classificar qualquer coisa porque leva um ponteiro de função para a função de comparação e passa um par de void * ' s.
Resposta
Como outras respostas apontaram, você pode usar void*
para estruturas de dados genéricas.Para outros tipos de polimorfismo paramétrico, macros de pré-processador eram usadas se algo se repetisse um lote (como dezenas de vezes). Para ser honesto, porém, na maioria das vezes para repetição moderada, as pessoas apenas copiavam e colavam e, em seguida, mudavam os tipos, porque há muitas armadilhas com macros que as tornam problemáticas.
Nós realmente precisamos de um nome para o inverso do paradoxo do blub , onde as pessoas têm dificuldade em imaginar programação em uma linguagem menos expressiva, porque isso aparece muito neste site. Se você nunca usou uma linguagem com maneiras expressivas de implementar polimorfismo paramétrico, não sabe realmente o que está perdendo. Você meio que aceita copiar e colar como algo chato, mas necessário.
Existem ineficiências nos idiomas atuais de sua escolha das quais você ainda nem está ciente. Em vinte anos, as pessoas estarão se perguntando como você os eliminou. A resposta curta é que você não sabia, porque não sabia que poderia.
Comentários
-
We really need a name for the opposite of the blub paradox, where people have a hard time imagining programming in a less expressive language, because this comes up a lot on this site.
Isso me parece exatamente o paradoxo do blub (" Linguagens menos poderosas que o Blub são obviamente menos poderosas, porque ' re faltando algum recurso que ele ' costumava usar. ") É ' d ser o oposto se a pessoa conhecesse as ' s limitações de seu idioma e pudesse imaginar recursos que as solucionassem. - O oposto não é ' t exatamente a palavra que eu procurava, mas ' é o mais próximo que consegui encontrar. O paradoxo do blub presume a habilidade de reconhecer uma linguagem menos poderosa, mas não ' t comenta sobre a dificuldade de compreender programação em 1. Um tanto contra-intuitivamente, a capacidade de programar em uma linguagem mais poderosa não ' t confere uma capacidade proporcional de programar em uma linguagem menos poderosa.
- Eu suspeito " converse " é a palavra que você ' está procurando: " Uma situação, objeto ou declaração que é o reverso de outro ou corresponde a ele, mas com certos termos transpostos "
Resposta
Lembro-me de quando o gcc foi enviado com genclass
– um programa que tomava como entrada um conjunto de tipos de parâmetro ( por exemplo, chave e valor para um Mapa) e um arquivo de sintaxe especial que descreve um tipo parametrizado (digamos, um Mapa ou um Vetor) e gera implementações C ++ válidas com os tipos de parâmetros preenchidos.
Então, se você necessário Map<int, string>
e Map<string, string>
(esta não era a sintaxe real, lembre-se) você tinha que execute esse programa duas vezes para gerar algo como map_string_string.h e map_int_string.h e, em seguida, use-os em seu código.
Consulte a página do manual para genclass
e a documentação da Biblioteca GNU C ++ 2.0 para obter mais detalhes.
Resposta
[Para o OP: Não estou tentando implicar com você pessoalmente, mas aumentar a sua consciência e a de outros “de pensar sobre a lógica da pergunta ( s) perguntado no SE e em outro lugar. Por favor, não leve isso para o lado pessoal!]
O título da pergunta é bom, mas você está limitando severamente o escopo de suas respostas ao incluir “… situações em que eles precisaram de geração de código em tempo de compilação. “Muitas boas respostas para a pergunta sobre como gerar código em tempo de compilação em C ++ sem modelos existem nesta página, mas para responder à pergunta que você originalmente fez:
O que as pessoas faziam antes dos modelos em C ++?
A resposta é, obviamente, eles (nós) não os utilizamos. Sim, estou sendo irônico, mas os detalhes da questão no corpo parecem (talvez exageradamente) presumir que todo mundo adora modelos e que nenhuma codificação poderia ter sido feita sem eles.
Por exemplo, concluí muitos projetos de codificação em várias linguagens sem precisar de geração de código em tempo de compilação e acredito que outros também fizeram. Claro, o problema resolvido por modelos foi uma coceira grande o suficiente para que alguém realmente o arranhou, mas o cenário apresentado por esta pergunta era, em grande parte, inexistente.
Considere uma pergunta semelhante em carros:
Como os motoristas mudavam de uma marcha para outra, usando um método automatizado que mudava de marcha para você, antes que a transmissão automática fosse inventada?
A pergunta é, obviamente, boba. Perguntar como uma pessoa fez X antes de X ser inventado não é realmente uma pergunta válida. A resposta geralmente é, “nós não fizemos isso e não perdemos porque não” sabíamos que existia “.Sim, é fácil ver o benefício após o fato, mas supor que todos estavam parados, chutando os calcanhares, esperando pela transmissão automática ou por modelos C ++, realmente não é verdade.
À pergunta, “como os motoristas trocavam de marcha antes da invenção da transmissão automática?”, Pode-se responder razoavelmente, “manualmente”, e esse é o tipo de resposta que você está obtendo aqui. Pode até ser o tipo de pergunta que você pretendia fazer.
Mas não foi a que você fez.
Então:
P: Como as pessoas usavam modelos antes de os modelos serem inventados?
R: Nós não.
P: Como as pessoas usavam modelos antes de os modelos serem inventados, quando precisavam usa modelos ?
R: Nós não precisamos usá-los. Por que presumir que sim? (Por que presumir que devemos?)
P: Quais são as formas alternativas de alcançar os resultados que os modelos fornecem?
R: Existem muitas respostas boas acima.
Por favor, pense nas falácias lógicas de suas postagens antes de postar.
[Obrigado! Por favor, não há intenção de prejudicar aqui.]
Comentários
- Acho que você ' estão sendo indevidamente pedantes. O OP não formulou sua pergunta tão bem quanto poderia. No entanto, acho ' muito claro que ele queria perguntar algo como " como as pessoas criavam funções genéricas antes modelos ". Acho que, neste caso, a ação apropriada teria sido editar sua resposta ou deixar um comentário em vez de dar uma palestra bastante condescendente.
Resposta
Macros horríveis estão certas, de http://www.artima.com/intv/modern2.html :
Bjarne Stroustrup: Sim. Quando você diz “modelo tipo T”, isso é realmente o antigo matemático, “para todo T.” É assim que é considerado. Meu primeiro artigo sobre “C com classes” (que evoluiu para C ++) de 1981 mencionou tipos parametrizados. Pronto, acertei o problema, mas acertei a solução totalmente errada. Eu expliquei como você pode parametrizar tipos com macros, e cara, esse era um código ruim.
Você pode ver como uma versão de macro antiga de um modelo foi usada aqui : http://www.xvt.com/sites/default/files/docs/Pwr++_Reference/rw/docs/html/toolsref/rwgvector.html
Resposta
Como Robert Harvey já disse, um ponteiro void é o tipo de dados genérico.
Um exemplo da biblioteca C padrão, como classificar um array de double com uma classificação genérica:
double *array = ...; int size = ...; qsort (array, size, sizeof (double), compare_doubles);
Onde compare_double
é definido como:
int compare_doubles (const void *a, const void *b) { const double *da = (const double *) a; const double *db = (const double *) b; return (*da > *db) - (*da < *db); }
A assinatura de qsort
é definida em stdlib.h:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *) );
Observe que não há tipo verificação em tempo de compilação, nem mesmo em tempo de execução. Se você classificar uma lista de strings com o comparador acima que espera duplas, ele tentará interpretar a representação binária de uma string como dupla e classificará de acordo.
Resposta
Uma maneira de fazer isso é assim:
https://github.com/rgeminas/gp–/blob/master/src/scope/darray.h
#define DARRAY_DEFINE(name, type) DARRAY_TYPEDECL(name, type) DARRAY_IMPL(name, type) // This is one single long line #define DARRAY_TYPEDECL(name, type) \ typedef struct darray_##name \ { \ type* base; \ size_t allocated_mem; \ size_t length; \ } darray_##name; // This is also a single line #define DARRAY_IMPL(name, type) \ static darray_##name* darray_init_##name() \ { \ darray_##name* arr = (darray_##name*) malloc(sizeof(darray_##name)); \ arr->base = (type*) malloc(sizeof(type)); \ arr->length = 0; \ arr->allocated_mem = 1; \ return arr; \ }
A macro DARRAY_TYPEDECL cria efetivamente uma definição de estrutura (em uma única linha), substituindo name
com o nome que você passou e armazenando uma matriz de type
que você passou (o name
está lá para que você possa concatená-lo ao nome da estrutura de base e ainda tem um identificador válido – darray_int * não é um nome válido para uma estrutura), enquanto a macro DARRAY_IMPL define as funções que operam nessa estrutura (nesse caso, elas “são marcadas como estáticas apenas para que possamos chame a definição apenas uma vez e não separe tudo).
Isso seria usado como:
#include "darray.h" // No types have been defined yet DARRAY_DEFINE(int_ptr, int*) // by this point, the type has been declared and its functions defined darray_int_ptr* darray = darray_int_ptr_init();
Resposta
Acho que os modelos são muito usados como uma forma de reutilizar os tipos de contêiner que têm um muitos valores algorítmicos, como matrizes dinâmicas (vetores), mapas, árvores, classificação etc., etc.
Sem modelos, necessariamente, eles contêm implementações são escritas de uma forma genérica e recebem informações suficientes sobre o tipo necessário para seu domínio. Por exemplo, com um vetor, eles só precisam que os dados estejam disponíveis e eles precisam saber o tamanho de cada item.
Digamos que você tenha uma classe de contêiner chamada Vector que faz isso. Leva nulo *. O uso simplista disso seria que o código da camada de aplicativo fizesse muitos cast. Portanto, se eles estão gerenciando objetos Cat, eles têm que lançar Cat * para anular * e voltar para todos os lugares. Preencher o código do aplicativo com casts tem problemas óbvios.
Os modelos resolvem isso.
Outra maneira de resolver isso é criar um tipo de contêiner personalizado para o tipo que você está armazenando no contêiner. Portanto, se você tiver uma classe Cat, poderá criar uma classe CatList derivada de Vector.Em seguida, você sobrecarrega os poucos métodos usados, apresentando versões que usam objetos Cat em vez de void *. Portanto, você “d sobrecarrega o método Vector :: Add (void *) com Cat :: Add (Cat *), que internamente simplesmente passa o parâmetro para Vector :: Add (). Em seguida, no código do seu aplicativo, você” d chama o versão sobrecarregada de Add ao passar em um objeto Cat e, assim, evitar a projeção. Para ser justo, o método Add não exigiria uma conversão porque um objeto Cat * se converte em void * sem uma conversão. Mas o método para recuperar um item, como a sobrecarga de índice ou um método Get (), sim.
A outra abordagem, o único exemplo do qual me lembro no início dos anos 90 com uma grande estrutura de aplicativo, é usar um utilitário personalizado que cria esses tipos sobre classes. Acredito que o MFC fez isso. Eles tinham classes diferentes para contêineres como CStringArray, CRectArray, CDoulbeArray, CIntArray, etc. Em vez de manter código duplicado, eles fizeram algum tipo de meta-programação semelhante a macros, usando uma ferramenta externa que geraria as classes. Eles disponibilizaram a ferramenta com Visual C ++ para o caso de alguém queria usá-lo – nunca usei. Talvez devesse. Mas, na época, os especialistas estavam anunciando, “Sane subconjunto de C ++” e “Você não precisa de modelos”