¿Es una mala práctica usar un compilador de C ++ solo para la sobrecarga de funciones?
En mi humilde opinión, sí, y tendré que volverme esquizofrénico para responder a esto ya que amo ambos idiomas, pero no tiene nada que ver con la eficiencia, sino más bien con la seguridad y el uso idiomático de los idiomas.
Lado C
Desde el punto de vista de C, considero que es un desperdicio hacer que su código requiera C ++ solo para usar la sobrecarga de funciones. A menos que lo esté utilizando para el polimorfismo estático con plantillas de C ++, es un azúcar sintáctico tan trivial obtenido a cambio de cambiar a un lenguaje completamente diferente. Además, si alguna vez desea exportar sus funciones a un dylib (puede o no ser una preocupación práctica), ya no puede hacerlo de manera muy práctica para un consumo generalizado con todos los símbolos de nombre mutilado.
Lado C ++
Desde el punto de vista de C ++, no debería usar C ++ como C con sobrecarga de funciones. Este no es un dogmatismo estilístico, sino uno relacionado con el uso práctico de C ++ cotidiano.
Su tipo normal de código C es razonablemente sensato y "seguro" para escribir si está trabajando en contra del sistema de tipo C que prohíbe cosas como los copiadores structs
. Una vez que esté trabajando en el sistema de tipos mucho más rico de C ++, las funciones diarias que son de gran valor como memset
y memcpy
no se convierten en funciones en las que debe apoyarse todo el tiempo. En cambio, son funciones que generalmente desea evitar como la peste, ya que con los tipos C ++, no debería tratarlos como bits y bytes sin procesar para copiar, barajar y liberar. Incluso si su código solo usa cosas como memset
primitivas y UDT de POD en este momento, en el momento en que alguien agrega un ctor a cualquier UDT que usa (incluso solo agrega un miembro que requiere uno, comostd::unique_ptr
miembro) contra tales funciones o una función virtual o algo por el estilo, hace que toda su codificación normal de estilo C sea susceptible a un comportamiento indefinido. Tómelo del propio Herb Sutter:
memcpy
y memcmp
violar el sistema de tipos. Usar memcpy
para copiar objetos es como ganar dinero usando una fotocopiadora. Usar memcmp
para comparar objetos es como comparar leopardos contando sus manchas. Puede parecer que las herramientas y los métodos hacen el trabajo, pero son demasiado groseros para hacerlo aceptablemente. Los objetos de C ++ tienen que ver con la ocultación de información (posiblemente el principio más rentable en ingeniería de software; consulte el Elemento 11): los objetos ocultan datos (consulte el Elemento 41) e idean abstracciones precisas para copiar esos datos a través de constructores y operadores de asignación (consulte los Elementos 52 a 55) . Arrasar todo eso memcpy
es una violación grave de la ocultación de información, y a menudo conduce a fugas de memoria y recursos (en el mejor de los casos), bloqueos (peor) o comportamiento indefinido (peor) - Estándares de codificación C ++.
Muchos desarrolladores de C no estarían de acuerdo con esto y con razón, ya que la filosofía solo se aplica si está escribiendo código en C ++. Lo más probable está escribiendo código muy problemático si se utilizan funciones como memcpy
todos los tiempos en el código que construye como C ++ , pero es perfectamente bien si lo hace en C . Los dos idiomas son muy diferentes a este respecto debido a las diferencias en el sistema de tipos. Es muy tentador observar el subconjunto de características que estos dos tienen en común y creer que uno puede usarse como el otro, especialmente en el lado de C ++, pero el código C + (o código C--) es generalmente mucho más problemático que C y C Código C ++.
Del mismo modo, no debería usar, digamos, malloc
en un contexto de estilo C (lo que implica que no hay EH) si puede llamar a cualquier función C ++ directamente que pueda lanzar, ya que tiene un punto de salida implícito en su función como resultado de excepción que no puede atrapar efectivamente escribiendo código de estilo C, antes de poder acceder a free
esa memoria. Así que cuando usted tiene un archivo que se acumula como C ++ con una .cpp
extensión o lo que sea y lo hace todo este tipo de cosas como malloc
, memcpy
, memset
,qsort
, etc., entonces está pidiendo problemas más adelante si no es así, a menos que sea el detalle de implementación de una clase que solo funciona con tipos primitivos, momento en el que todavía necesita hacer un manejo de excepciones para estar seguro de excepciones. Si estás escribiendo código C ++ por el contrario quiere generalmente se basan en RAII y usar cosas como vector
, unique_ptr
, shared_ptr
, etc, y evitar todos normales de codificación de estilo C, cuando sea posible.
La razón por la que puede jugar con cuchillas de afeitar en los tipos de datos de rayos X y C y jugar con sus bits y bytes sin ser propenso a causar daños colaterales en un equipo (aunque todavía puede lastimarse de cualquier manera) no es por lo que C los tipos pueden hacer, pero debido a lo que nunca podrán hacer. En el momento en que extienda el sistema de tipos de C para incluir características de C ++ como ctors, dtors y vtables, junto con el manejo de excepciones, todo el código idiomático de C se volverá mucho, mucho más peligroso de lo que es actualmente, y verá un nuevo tipo de evolución de la filosofía y la mentalidad que fomentará un estilo de codificación completamente diferente, como puede ver en C ++, que ahora considera incluso usar una mala práctica de puntero en bruto para una clase que gestiona la memoria en lugar de, por ejemplo, un recurso conforme a RAII como unique_ptr
. Esa mentalidad no evolucionó a partir de una sensación absoluta de seguridad. Evolucionó a partir de lo que C ++ necesita específicamente para protegerse contra características como el manejo de excepciones dado lo que simplemente permite a través de su sistema de tipos.
Excepción-Seguridad
Nuevamente, en el momento en que esté en C ++ land, la gente esperará que su código sea seguro para las excepciones. Las personas pueden mantener su código en el futuro, dado que ya está escrito y compilado en C ++, y simplemente usar std::vector, dynamic_cast, unique_ptr, shared_ptr
, etc., en código llamado directa o indirectamente por su código, creyendo que es inocuo ya que su código ya es "supuestamente" C ++ código. En ese punto, tenemos que enfrentar la posibilidad de que las cosas salgan mal, y luego, cuando tomas un código C perfecto y encantador 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;
}
... ahora está roto. Necesita ser reescrito para ser seguro para excepciones:
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! Es por eso que la mayoría de los desarrolladores de C ++ exigirían esto en su lugar:
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.
}
Lo anterior es un código seguro de excepción conforme a RAII del tipo que los desarrolladores de C ++ generalmente aprobarían, ya que la función no perderá ninguna línea de código que desencadene una salida implícita como resultado de a throw
.
Elige un idioma
Debe adoptar el sistema de tipos y la filosofía de C ++ con RAII, excepción de seguridad, plantillas, OOP, etc., o adoptar C, que gira en gran medida en torno a bits y bytes sin formato. No debe formar un matrimonio impío entre estos dos idiomas, y en su lugar separarlos en idiomas distintos para que sean tratados de manera muy diferente en lugar de difuminarlos.
Estos idiomas quieren casarse contigo. Por lo general, debes elegir uno en lugar de salir y perder el tiempo con ambos. O puedes ser un polígamo como yo y casarte con ambos, pero debes cambiar completamente tu forma de pensar cuando pasas tiempo uno con el otro y mantenerlos bien separados entre sí para que no peleen entre ellos.
Tamaño binario
Solo por curiosidad, traté de tomar mi implementación de lista gratuita y punto de referencia en este momento y portarlo a C ++ ya que tenía mucha curiosidad sobre esto:
[...] no sé cómo sería para C porque no he estado usando el compilador de C.
... y quería saber si el tamaño binario se inflaría simplemente construyéndose como C ++. Me obligó a esparcir lanzamientos explícitos por todo el lugar, lo cual era fugitivo (una razón por la que me gusta escribir cosas de bajo nivel como asignadores y estructuras de datos en C mejor) pero solo me llevó un minuto.
Esto era solo comparar una compilación de lanzamiento MSVC de 64 bits para una aplicación de consola simple y con un código que no usaba ninguna función de C ++, ni siquiera la sobrecarga del operador, solo la diferencia entre construirlo con C y usar, por ejemplo, en <cstdlib>
lugar de <stdlib.h>
y cosas así, pero me sorprendió descubrir que no hizo ninguna diferencia en el tamaño binario.
El binario era 9,728
bytes cuando se construyó en C, y también 9,278
bytes cuando se compiló como código C ++. En realidad no esperaba eso. Pensé que cosas como EH al menos agregarían un poco allí (pensé que al menos serían como cien bytes diferentes), aunque probablemente fue capaz de darse cuenta de que no había necesidad de agregar instrucciones relacionadas con EH ya que solo estoy usando la biblioteca estándar C y nada arroja. Pensé algoagregaría un poco al tamaño binario de cualquier manera, como RTTI. De todos modos, fue genial ver eso. Por supuesto, no creo que debas generalizar a partir de este resultado, pero al menos me impresionó un poco. Tampoco tuvo ningún impacto en los puntos de referencia, y naturalmente, ya que me imagino que el tamaño binario resultante idéntico también significó instrucciones de máquina idénticas.
Dicho esto, ¿a quién le importa el tamaño binario con los problemas de seguridad e ingeniería mencionados anteriormente? Así que de nuevo, elige un idioma y adopta su filosofía en lugar de tratar de bastardarlo; Eso es lo que recomiendo.
//
comentarios. Si funciona, ¿por qué no?