programación - programacion c++ ejemplos
Pasar de C++ a C (8)
Creo que el principal problema por el que c ++ es más difícil de aceptar en un entorno integrado es la falta de ingenieros que entiendan cómo usar c ++ correctamente.
Sí, el mismo razonamiento se puede aplicar a C también, pero afortunadamente no hay tantas trampas en C que puedan dispararse en el pie. C ++, por otro lado, necesita saber cuándo no usar ciertas funciones en c ++.
En general, me gusta c ++. Lo uso en la capa de servicios O / S, el controlador, el código de gestión, etc. Pero si su equipo no tiene suficiente experiencia con él, será un desafío difícil.
Tuve experiencia con ambos. Cuando el resto del equipo no estaba preparado, fue un desastre total. Por otro lado, fue una buena experiencia.
Después de unos años de codificación en C ++, me ofrecieron recientemente una codificación de trabajo en C, en el campo incrustado.
Dejando de lado la cuestión de si es correcto o incorrecto descartar C ++ en el campo incrustado, hay algunas características / modismos en C ++. Extrañaría mucho. Sólo para nombrar unos pocos:
- Estructuras de datos genéricas y seguras para tipos (usando plantillas).
- RAII. Especialmente en funciones con múltiples puntos de retorno, por ejemplo, no tener que recordar liberar el mutex en cada punto de retorno.
- Destructores en general. Es decir, si escribes un disco una vez para MyClass, si una instancia de MyClass es miembro de MyOtherClass, MyOtherClass no tiene que desinicializar explícitamente la instancia de MyClass, sino que se llama automáticamente.
- Espacios de nombres
¿Cuáles son sus experiencias al pasar de C ++ a C?
¿Qué sustitutos C encontraste para tus características / expresiones idiomáticas favoritas de C ++? ¿Descubriste alguna característica C que quisieras que tuviera C ++?
En mi línea de trabajo, que está incrustado, por cierto, estoy constantemente cambiando entre C y C ++.
Cuando estoy en C, extraño C ++:
plantillas (incluidos, entre otros, contenedores STL). Los uso para cosas como contadores especiales, grupos de buffer, etc. (construí mi propia biblioteca de plantillas de clases y plantillas de funciones que uso en diferentes proyectos integrados)
biblioteca estándar muy poderosa
destructores, que por supuesto hacen RAII posible (mutexes, interrupción de deshabilitar, seguimiento, etc.)
especificadores de acceso, para reforzar mejor quién puede usar (no ver) qué
Utilizo la herencia en proyectos más grandes, y el soporte integrado de C ++ es mucho más limpio y más agradable que el "truco" de incrustación de la clase base como primer miembro (sin mencionar la invocación automática de constructores, listas de inicio, etc. ) pero los elementos enumerados anteriormente son los que más extraño.
Además, probablemente solo alrededor de un tercio de los proyectos incrustados de C ++ en los que trabajo usan excepciones, así que me he acostumbrado a vivir sin ellos, así que no los extraño demasiado cuando vuelvo a C.
Por otro lado, cuando vuelvo a un proyecto de C con un número significativo de desarrolladores, hay clases completas de problemas de C ++ que estoy acostumbrado a explicar a las personas que desaparecen. Principalmente problemas debido a la complejidad de C ++, y personas que creen saber lo que está sucediendo, pero que están realmente en la parte "C con clases" de la curva de confianza de C ++ .
Dada la opción, preferiría usar C ++ en un proyecto, pero solo si el equipo es bastante sólido en el idioma. También, por supuesto, suponiendo que no es un proyecto de 8K μC donde de todos modos estoy escribiendo "C".
La diferencia entre C y C ++ es la predictibilidad del comportamiento del código.
Es más fácil predecir con gran precisión qué hará tu código en C, en C ++ podría ser un poco más difícil obtener una predicción exacta.
La previsibilidad en C le da un mejor control de lo que está haciendo su código, pero eso también significa que tiene que hacer más cosas.
En C ++ puedes escribir menos código para hacer lo mismo, pero (al menos para mí) Tengo problemas ocasionalmente para saber cómo se presenta el código objeto en la memoria y su comportamiento esperado.
Me mudé de C ++ a C por una razón diferente (algún tipo de reacción alérgica;) y hay solo algunas cosas que extraño y algunas cosas que obtuve. Si se apega a C99, si puede, hay construcciones que le permiten programar de manera bastante agradable y segura, en particular
- los inicializadores designados (eventualmente combinados con macros) hacen que la inicialización de clases simples sea tan sencilla como la de los constructores
- literales compuestos para variables temporales
-
for
variable -scope puede ayudarlo a gestionar el alcance de recursos , en particular para asegurar elunlock
de mutexes ofree
de matrices, incluso en devoluciones de funciones preliminares -
__VA_ARGS__
macros__VA_ARGS__
se pueden usar para tener argumentos por defecto para las funciones y para desenrollar el código - funciones en
inline
y macros que se combinan bien para reemplazar (tipo de) funciones sobrecargadas
Nada como el STL existe para C.
Hay libs disponibles que proporcionan una funcionalidad similar, pero ya no se integran.
Creo que sería uno de mis mayores problemas ... Saber con qué herramienta podría resolver el problema, pero sin tener las herramientas disponibles en el idioma que tengo que usar.
Prácticamente las mismas razones que tengo para usar C ++ o una mezcla de C / C ++ en lugar de C puro. Puedo vivir sin espacios de nombres, pero los uso todo el tiempo si el código estándar lo permite. La razón es que puedes escribir código mucho más compacto en C ++. Esto es muy útil para mí, escribo servidores en C ++ que tienden a bloquearse de vez en cuando. En ese punto, ayuda mucho si el código que está mirando es breve y consiste. Por ejemplo, considere el siguiente código:
uint32_t
ScoreList::FindHighScore(
uint32_t p_PlayerId)
{
MutexLock lock(m_Lock);
uint32_t highScore = 0;
for(int i = 0; i < m_Players.Size(); i++)
{
Player& player = m_Players[i];
if(player.m_Score > highScore)
highScore = player.m_Score;
}
return highScore;
}
En C que se ve así:
uint32_t
ScoreList_getHighScore(
ScoreList* p_ScoreList)
{
uint32_t highScore = 0;
Mutex_Lock(p_ScoreList->m_Lock);
for(int i = 0; i < Array_GetSize(p_ScoreList->m_Players); i++)
{
Player* player = p_ScoreList->m_Players[i];
if(player->m_Score > highScore)
highScore = player->m_Score;
}
Mutex_UnLock(p_ScoreList->m_Lock);
return highScore;
}
No es un mundo de diferencia. Una línea más de código, pero tiende a sumarse. Nomalmente haces todo lo posible para mantenerlo limpio y delgado, pero a veces tienes que hacer algo más complejo. Y en esas situaciones, valoras tu recuento de líneas. Una línea más es una cosa más a tener en cuenta cuando intenta averiguar por qué su red de transmisión deja de transmitir mensajes de repente.
De todos modos, me parece que C ++ me permite hacer cosas más complejas de manera segura.
Un par de observaciones
- A menos que planee usar su compilador de c ++ para compilar su C (que es posible si se ajusta a un subconjunto bien definido de C ++) pronto descubrirá cosas que su compilador permite en C que sería un error de compilación en C ++.
- No más errores de plantilla crípticos (¡yay!)
- No (lenguaje compatible) programación orientada a objetos
Trabajando en un proyecto incrustado, intenté trabajar en C una sola vez, y no pude soportarlo. Era tan detallado que dificultaba leer cualquier cosa. Además, me gustaron los contenedores optimizados para incrustación que había escrito, que tenían que convertirse en bloques mucho menos seguros y difíciles de reparar #define
.
Código que en C ++ se veía así:
if(uart[0]->Send(pktQueue.Top(), sizeof(Packet)))
pktQueue.Dequeue(1);
se convierte en:
if(UART_uchar_SendBlock(uart[0], Queue_Packet_Top(pktQueue), sizeof(Packet)))
Queue_Packet_Dequeue(pktQueue, 1);
lo cual mucha gente probablemente dirá que está bien, pero se pone ridículo si tiene que hacer más de un par de llamadas al "método" en una línea. Dos líneas de C ++ se convertirían en cinco de C (debido a los límites de longitud de línea de 80 caracteres). Ambos generarían el mismo código, ¡así que no es como si al procesador objetivo le importara!
Una vez (en 1995), traté de escribir una gran cantidad de C para un programa de procesamiento de datos multiprocesador. El tipo en el que cada procesador tiene su propia memoria y programa. El compilador suministrado por el proveedor era un compilador de C (algún tipo de derivado HighC), sus bibliotecas eran de código cerrado, por lo que no pude usar GCC para compilar, y sus API se diseñaron con la idea de que sus programas serían principalmente el proceso de inicialización. / terminar la variedad, por lo que la comunicación entre los procesadores era, en el mejor de los casos, rudimentaria.
Llegué alrededor de un mes antes de cfront vencido, encontré una copia de cfront y lo cfront en los makefiles para poder usar C ++. Cfront ni siquiera soportaba plantillas, pero el código de C ++ era mucho, mucho más claro.
Estructuras de datos genéricas y seguras para tipos (usando plantillas).
Lo más parecido que C tiene a las plantillas es declarar un archivo de encabezado con un montón de código que se ve así:
TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{ /* ... */ }
luego jálalo con algo como:
#define TYPE Packet
#include "Queue.h"
#undef TYPE
Tenga en cuenta que esto no funcionará para los tipos compuestos (p. Ej., No colas de caracteres unsigned char
) a menos que primero haga un typedef
.
Ah, y recuerda, si este código no se usa en ninguna parte, ni siquiera sabes si es sintácticamente correcto.
EDITAR: Una cosa más: tendrás que administrar manualmente la instanciación del código. Si su código de "plantilla" no es todas las funciones en línea, entonces tendrá que poner un poco de control para asegurarse de que las cosas se instancian una sola vez para que su enlazador no escuche un montón de errores de "múltiples instancias de Foo" .
Para hacer esto, deberá colocar las cosas que no están en la línea en una sección de "implementación" en su archivo de encabezado:
#ifdef implementation_##TYPE
/* Non-inlines, "static members", global definitions, etc. go here. */
#endif
Y luego, en un lugar en todo su código por variante de plantilla , debe:
#define TYPE Packet
#define implementation_Packet
#include "Queue.h"
#undef TYPE
Además, esta sección de implementación debe estar fuera del estándar #ifndef
/ #define
/ #endif
litany, porque puede incluir el archivo de encabezado de plantilla en otro archivo de encabezado, pero necesita crear una instancia posterior en un archivo .c
.
Sí, se pone feo rápido. Por eso la mayoría de los programadores de C ni siquiera lo intentan.
RAII.
Especialmente en funciones con múltiples puntos de retorno, por ejemplo, no tener que recordar liberar el mutex en cada punto de retorno.
Bueno, olvida tu lindo código y acostúmbrate a todos tus puntos de retorno (excepto el final de la función) siendo goto
s:
TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{
TYPE * result;
Mutex_Lock(this->lock);
if(this->head == this->tail)
{
result = 0;
goto Queue_##TYPE##_Top_exit:;
}
/* Figure out `result` for real, then fall through to... */
Queue_##TYPE##_Top_exit:
Mutex_Lock(this->lock);
return result;
}
Destructores en general.
Es decir, si escribes un disco una vez para MyClass, si una instancia de MyClass es miembro de MyOtherClass, MyOtherClass no tiene que desinicializar explícitamente la instancia de MyClass, sino que se llama automáticamente.
La construcción de objetos debe manejarse explícitamente de la misma manera.
Espacios de nombres
De hecho, es algo sencillo de arreglar: solo inserte un prefijo en cada símbolo. Esta es la causa principal de la saturación de la fuente de la que hablé antes (dado que las clases son espacios de nombres implícitos). La gente de C ha estado viviendo esto, bueno, para siempre, y probablemente no vean cuál es el problema.
YMMV