É uma má prática usar um compilador C ++ apenas para sobrecarga de função?
Ponto de vista do IMHO, sim, e precisarei me tornar esquizofrênico para responder a este, já que amo os dois idiomas, mas não tem nada a ver com eficiência, mas mais como segurança e uso idiomático de idiomas.
Lado C
Do ponto de vista de C, acho tão inútil fazer com que seu código exija C ++ apenas para usar a sobrecarga de funções. A menos que você o esteja utilizando para polimorfismo estático com modelos C ++, é um açúcar sintático tão trivial obtido em troca da mudança para uma linguagem totalmente diferente. Além disso, se você quiser exportar suas funções para um dylib (pode ou não ser uma preocupação prática), não poderá mais fazê-lo na prática para um consumo generalizado com todos os símbolos com nomes diferentes.
Lado C ++
Do ponto de vista de C ++, você não deve usar C ++ como C com sobrecarga de função. Este não é um dogmatismo estilístico, mas um relacionado ao uso prático do C ++ cotidiano.
Seu tipo normal de código C é razoavelmente sensato e "seguro" para escrever se você estiver trabalhando com o sistema de tipo C que proíbe coisas como copiadores de conteúdo structs
. Quando você trabalha no sistema de tipos muito mais rico em C ++, as funções diárias que são de enorme valor memset
e memcpy
não se tornam funções nas quais você deve se apoiar o tempo todo. Em vez disso, são funções que você geralmente deseja evitar como uma praga, já que com os tipos C ++, você não deve tratá-los como bits e bytes brutos para serem copiados, embaralhados e liberados. Mesmo que seu código use apenas coisas como memset
primitivas e UDTs de POD no momento, no momento em que alguém adicionar um ctor a qualquer UDT que você usar (incluindo apenas a adição de um membro que exija um, comostd::unique_ptr
membro) contra essas funções ou uma função virtual ou qualquer coisa desse tipo, torna toda a sua codificação normal no estilo C suscetível a comportamento indefinido. Tomemos do próprio Herb Sutter:
memcpy
e memcmp
violar o sistema de tipos. Usar memcpy
para copiar objetos é como ganhar dinheiro usando uma fotocopiadora. Usar memcmp
para comparar objetos é como comparar leopardos contando seus pontos. As ferramentas e métodos podem parecer fazer o trabalho, mas são muito grosseiros para fazê-lo de maneira aceitável. Objetos C ++ têm tudo a ver com ocultação de informações (sem dúvida o princípio mais lucrativo em engenharia de software; consulte o Item 11): os objetos ocultam dados (consulte o Item 41) e criam abstrações precisas para copiar esses dados por meio de construtores e operadores de atribuição (consulte os itens 52 a 55) . Fazer um bulldozing sobre tudo isso com memcpy
uma violação grave da ocultação de informações e geralmente leva a vazamentos de memória e recursos (na melhor das hipóteses), falhas (pior) ou comportamento indefinido (pior) - Padrões de Codificação C ++.
Muitos desenvolvedores de C discordariam disso e com razão, pois a filosofia só se aplica se você estiver escrevendo código em C ++. Você provavelmente está escrevendo código muito problemático se você usar funções como memcpy
todos os tempos no código que constrói como C ++ , mas é perfeitamente bem se você fizer isso em C . Os dois idiomas são muito diferentes nesse aspecto, devido às diferenças no sistema de tipos. É muito tentador olhar para o subconjunto de recursos que esses dois têm em comum e acredita que um pode ser usado como o outro, especialmente no lado C ++, mas o código C + (ou código C--) geralmente é muito mais problemático do que C e Código C ++.
Da mesma forma, você não deve estar usando, digamos, malloc
em um contexto de estilo C (que não implica EH) se ele puder chamar diretamente qualquer função C ++ que possa ser lançada, desde então, você tem um ponto de saída implícito em sua função como resultado do exceção que você não pode pegar efetivamente escrevendo código em estilo C, antes de poder free
acessar essa memória. Assim, sempre que você tem um arquivo que constrói como C ++ com uma .cpp
extensão ou qualquer outra coisa e ele faz todos esses tipos de coisas como malloc
, memcpy
, memset
,qsort
, etc, então ele está solicitando problemas mais adiante, se não já, a menos que sejam os detalhes de implementação de uma classe que funcione apenas com tipos primitivos; nesse momento, ainda é necessário fazer o tratamento de exceções para garantir sua segurança. Se você estiver escrevendo código C ++ você em vez quer confiar geralmente em RAII e usar coisas como vector
, unique_ptr
, shared_ptr
, etc, e evitar qualquer codificação normais de estilo C, quando possível.
A razão pela qual você pode brincar com lâminas de barbear nos tipos de dados C e raios-X e brincar com seus bits e bytes sem propensão a causar danos colaterais em uma equipe (embora você ainda possa se machucar de qualquer maneira) não é por causa do que C tipos podem fazer, mas por causa do que eles nunca serão capazes de fazer. No momento em que você estende o sistema de tipos do C para incluir recursos do C ++, como ctors, dtors e vtables, junto com o tratamento de exceções, todo o código C idiomático seria renderizado muito, muito mais perigoso do que é atualmente, e você verá um novo tipo de evolução da filosofia e da mentalidade que encorajará um estilo de codificação completamente diferente, como você vê em C ++, que agora considera o uso de uma má prática bruta de ponteiro para uma classe que gerencia a memória em vez de, por exemplo, um recurso compatível com RAII unique_ptr
. Essa mentalidade não evoluiu de uma sensação absoluta de segurança. Ele evoluiu a partir do que o C ++ especificamente precisa ser seguro contra recursos como o tratamento de exceções, considerando o que apenas permite através de seu sistema de tipos.
Exceção-Segurança
Novamente, no momento em que você estiver no país C ++, as pessoas esperam que seu código seja seguro contra exceções. As pessoas podem manter seu código no futuro, já que ele já está escrito e compilado em C ++ e simplesmente usa std::vector, dynamic_cast, unique_ptr, shared_ptr
etc. no código chamado direta ou indiretamente pelo seu código, acreditando ser inócuo, pois seu código já é "supostamente" C ++ código. Nesse ponto, temos que enfrentar a chance de as coisas acontecerem e, então, quando você usar um código C perfeitamente bom e adorável, como este:
int some_func(int n, ...)
{
int* x = calloc(n, sizeof(int));
if (x)
{
f(n, x); // some function which, now being a C++ function, may
// throw today or in the future.
...
free(x);
return success;
}
return fail;
}
... agora está quebrado. Ele precisa ser reescrito para ter uma exceção segura:
int some_func(int n, ...)
{
int* x = calloc(n, sizeof(int));
if (x)
{
try
{
f(n, x); // some function which, now being a C++ function, may
// throw today or in the future (maybe someone used
// std::vector inside of it).
}
catch (...)
{
free(x);
throw;
}
...
free(x);
return success;
}
return fail;
}
Bruto! É por isso que a maioria dos desenvolvedores de C ++ exigiria isso:
void some_func(int n, ...)
{
vector<int> x(n);
f(x); // some function which, now being a C++ function, may throw today
// or in the future.
}
O código acima é um código de exceção segura compatível com RAII, do tipo que os desenvolvedores de C ++ geralmente aprovariam, pois a função não vazará, nenhuma linha de código que desencadeia uma saída implícita como resultado de a throw
.
Escolha um idioma
Você deve adotar o sistema de tipos e a filosofia do C ++ com RAII, segurança de exceção, modelos, OOP etc. ou adotar o C, que gira amplamente em torno de bits e bytes brutos. Você não deve formar um casamento profano entre esses dois idiomas e, em vez disso, separá-los em idiomas distintos para serem tratados de maneira muito diferente em vez de confundi-los.
Esses idiomas querem se casar com você. Você geralmente tem que escolher um em vez de namorar e brincar com os dois. Ou você pode ser um polígamo como eu e se casar com os dois, mas precisa mudar completamente seu pensamento ao passar um tempo um com o outro e mantê-los bem separados um do outro para que não briguem.
Tamanho binário
Por curiosidade, tentei pegar minha implementação de lista grátis e referência agora e portá-la em C ++, pois fiquei realmente curiosa sobre isso:
[...] não sei como seria o C porque não uso o compilador C.
... e queria saber se o tamanho binário seria inflado apenas criando como C ++. Isso exigiu que eu espalhasse elencos explícitos em todo o lugar, que era fugaz (uma razão pela qual eu gosto de escrever coisas de baixo nível, como alocadores e estruturas de dados em C, melhor), mas levou apenas um minuto.
Isso estava apenas comparando uma compilação de versão de MSVC de 64 bits para um aplicativo simples de console e com código que não usava nenhum recurso de C ++, nem mesmo sobrecarga de operador - apenas a diferença entre compilar com C e usar, digamos, em <cstdlib>
vez de <stdlib.h>
e coisas assim, mas fiquei surpreso ao descobrir que não fazia diferença nenhuma no tamanho binário!
O binário era 9,728
bytes quando foi criado em C e da mesma forma 9,278
bytes quando compilado como código C ++. Na verdade, eu não esperava isso. Eu pensei que coisas como EH ao menos adicionariam um pouco lá (pensei que seria pelo menos cem bytes diferentes), embora provavelmente ele fosse capaz de descobrir que não havia necessidade de adicionar instruções relacionadas ao EH, pois eu sou apenas usando a biblioteca padrão C e nada lança. Eu pensei em algoadicionaria um pouco ao tamanho binário de qualquer maneira, como RTTI. Enfim, foi legal ver isso. É claro que não acho que você deva generalizar a partir desse resultado, mas pelo menos isso me impressionou um pouco. Também não causou impacto nos benchmarks, e naturalmente, pois eu imagino que o tamanho binário resultante idêntico também significou instruções de máquina resultantes idênticas.
Dito isto, quem se importa com o tamanho binário dos problemas de segurança e engenharia mencionados acima? Então, novamente, escolha uma linguagem e adote sua filosofia em vez de tentar bastardizá-la; é isso que eu recomendo.
//
comentários. Se funcionar, por que não?