c++ c comma-operator

c++ - Usos del operador de coma C



comma-operator (20)

Esta pregunta ya tiene una respuesta aquí:

Lo ves utilizado en las declaraciones de bucle, pero es la sintaxis legal en cualquier lugar. ¿Qué usos has encontrado en otro lugar, si hay alguno?


A menudo lo uso para ejecutar una función de inicializador estático en algunos archivos cpp, para evitar problemas de incialización vagos con singletons clásicos:

void* s_static_pointer = 0; void init() { configureLib(); s_static_pointer = calculateFancyStuff(x,y,z); regptr(s_static_pointer); } bool s_init = init(), true; // just run init() before anything else Foo::Foo() { s_static_pointer->doStuff(); // works properly }


A veces se usa en macros, como macros de depuración como este:

#define malloc(size) (printf("malloc(%d)/n", (int)(size)), malloc((size)))

(Pero fíjate en este horrible fracaso , verdaderamente tuyo, por lo que puede pasar cuando te excedas).

Pero a menos que realmente lo necesite, o esté seguro de que hace que el código sea más legible y mantenible, recomendaría no usar el operador de coma.


Creo que, en general, la coma de C no es un buen estilo de usar, simplemente porque es muy fácil pasarla por alto, ya sea por alguien más que intenta leer / entender / corregir su código, o usted mismo un mes después. Fuera de declaraciones variables y para bucles, por supuesto, donde es idiomático.

Puede usarlo, por ejemplo, para empaquetar varias instrucciones en un operador ternario (? :), ala:

int x = some_bool ? printf("WTF"), 5 : fprintf(stderr, "No, really, WTF"), 117;

pero mis dioses, ¿por qué?!? (Lo he visto usado de esta manera en código real, pero no tengo acceso a él para mostrarlo desafortunadamente)


Dada la cita de @Nicolas Goy del estándar, entonces parece que podrías escribir una línea para bucles como:

int a, b, c; for(a = 0, b = 10; c += 2*a+b, a <= b; a++, b--); printf("%d", c);

Pero, Dios mío, hombre, ¿de verdad quieres que tu código C sea más oscuro de esta manera?


Del estándar C:

El operando izquierdo de un operador de coma se evalúa como una expresión vacía; hay un punto de secuencia después de su evaluación. Entonces se evalúa el operando correcto; el resultado tiene su tipo y valor. (Un operador de coma no produce un valor l).) Si se intenta modificar el resultado de un operador de coma o acceder a él después del siguiente punto de secuencia, el comportamiento no está definido.

En resumen, le permite especificar más de una expresión donde C solo espera una. Pero en la práctica se usa principalmente en bucles for.

Tenga en cuenta que:

int a, b, c;

NO es el operador de coma, es una lista de declaradores.


Dos características del operador de coma asesino en C ++:

a) Leer de la transmisión hasta que se encuentre una cadena específica (ayuda a mantener el código SECO):

while (cin >> str, str != "STOP") { //process str }

b) Escribir código complejo en los inicializadores del constructor:

class X : public A { X() : A( (global_function(), global_result) ) {}; };


El lenguaje C (así como C ++) es históricamente una mezcla de dos estilos de programación completamente diferentes, a los que se puede llamar "programación de expresiones" y "programación de expresiones". Como sabe, cada lenguaje de programación de procedimientos normalmente admite construcciones fundamentales como la secuenciación y la ramificación . Estas construcciones fundamentales están presentes en los lenguajes C / C ++ en dos formas: una para la programación de enunciados, otra para la programación de expresiones.

Por ejemplo, cuando escribe su programa en términos de enunciados, puede usar una secuencia de enunciados separados por ; . Cuando desee hacer una bifurcación, use if statements. También puede usar ciclos y otros tipos de instrucciones de transferencia de control.

En la programación de expresiones, los mismos constructos están disponibles para usted también. Aquí es donde el operador entra en juego. El operador , en nada más que un separador de expresiones secuenciales en C, es decir , operador , en la programación de expresiones cumple la misma función que ; lo hace en la programación de enunciados. La ramificación en la programación de expresiones se realiza a través del operador ?: Y, alternativamente, a través de las propiedades de evaluación de cortocircuito de && y || operadores. (Sin embargo, la programación de expresiones no tiene ciclos. Y para reemplazarlas con recursión tendrías que aplicar programación de instrucciones).

Por ejemplo, el siguiente código

a = rand(); ++a; b = rand(); c = a + b / 2; if (a < c - 5) d = a; else d = b;

que es un ejemplo de programación de enunciados tradicional, se puede volver a escribir en términos de programación de expresiones como

a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? d = a : d = b;

o como

a = rand(), ++a, b = rand(), c = a + b / 2, d = a < c - 5 ? a : b;

o

d = (a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? a : b);

o

a = rand(), ++a, b = rand(), c = a + b / 2, (a < c - 5 && (d = a, 1)) || (d = b);

Huelga decir que, en la práctica, la programación de enunciados generalmente produce código C / C ++ mucho más legible, por lo que normalmente usamos programación de expresiones en cantidades muy bien medidas y restringidas. Pero en muchos casos es útil. Y la línea entre lo aceptable y lo que no es, en gran medida, una cuestión de preferencia personal y la capacidad de reconocer y leer los modismos establecidos.

Como nota adicional: el propio diseño del lenguaje está obviamente adaptado a las declaraciones. Los enunciados pueden invocar libremente expresiones, pero las expresiones no pueden invocar declaraciones (aparte de llamar funciones predefinidas). Esta situación se modifica de una manera bastante interesante en el compilador de GCC, que admite las denominadas "expresiones de enunciado" como una extensión (simétrica a "enunciados de expresión" en el estándar C). Las "expresiones de enunciado" permiten al usuario insertar directamente el código basado en sentencias en expresiones, al igual que pueden insertar código basado en expresiones en declaraciones en C. estándar.

Como otra nota adicional: en lenguaje C ++, la programación basada en funtores desempeña un papel importante, que puede verse como otra forma de "programación de expresiones". De acuerdo con las tendencias actuales en el diseño de C ++, podría considerarse preferible a la programación tradicional de enunciados en muchas situaciones.


En general, evito usar el operador de coma porque hace que el código sea menos legible. En casi todos los casos, sería más simple y más claro hacer dos afirmaciones. Me gusta:

foo=bar*2, plugh=hoo+7;

no ofrece una clara ventaja sobre:

foo=bar*2; plugh=hoo+7;

El único lugar además de los bucles donde lo he usado en construcciones if / else, como:

if (a==1) ... do something ... else if (function_with_side_effects_including_setting_b(), b==2) ... do something that relies on the side effects ...

Puede poner la función antes del IF, pero si la función tarda mucho tiempo en ejecutarse, puede evitar hacerlo si no es necesario, y si la función no debe hacerse a menos que a! = 1, entonces eso no es un opción. La alternativa es anidar el IF una capa adicional. Eso es lo que suelo hacer porque el código anterior es un poco críptico. Pero lo hice de vez en cuando, porque anidar es críptico.


Es muy útil para agregar algunos comentarios a las macros ASSERT :

ASSERT(("This value must be true.", x));

Dado que la mayoría de las macros de estilo assert arrojarán el texto completo de su argumento, esto agrega un poco más de información útil en la afirmación.


Fuera de un bucle for, e incluso puede haber un olor a código, el único lugar que he visto como un buen uso para el operador de coma es como parte de una eliminación:

delete p, p = 0;

El único valor sobre la alternativa es que puede copiar / pegar accidentalmente solo la mitad de esta operación si está en dos líneas.

También me gusta porque si lo haces por hábito, nunca olvidarás la asignación de cero. (Por supuesto, por qué p no está dentro de alguna clase de envoltorio auto_ptr, smart_ptr, shared_ptr, etc. es una pregunta diferente).


La única vez que he visto el operador utilizado fuera de un bucle for fue realizar una afirmación en una declaración ternaria. Fue hace mucho tiempo, así que no puedo recordar la declaración exacta, pero fue algo así como:

int ans = isRunning() ? total += 10, newAnswer(total) : 0;

Obviamente, ninguna persona en su sano juicio escribiría un código como este, pero el autor era un genio malvado que construía declaraciones c basadas en el código ensamblador que generaban, no en la legibilidad. Por ejemplo, a veces usaba loops en lugar de if porque prefería el ensamblador que generaba.

Su código era muy rápido pero no se podía mantener, me alegro de no tener que trabajar más con él.


La biblioteca Boost Assignment es un buen ejemplo de sobrecarga del operador de coma de una manera útil y legible. Por ejemplo:

using namespace boost::assign; vector<int> v; v += 1,2,3,4,5,6,7,8,9;



Lo he usado para una macro para "asignar un valor de cualquier tipo a un búfer de salida señalado por un char *, y luego incrementar el puntero por el número requerido de bytes", como este:

#define ASSIGN_INCR(p, val, type) ((*((type) *)(p) = (val)), (p) += sizeof(type))

Usar el operador de coma significa que la macro se puede usar en expresiones o como enunciados como se desee:

if (need_to_output_short) ASSIGN_INCR(ptr, short_value, short); latest_pos = ASSIGN_INCR(ptr, int_value, int); send_buff(outbuff, (int)(ASSIGN_INCR(ptr, last_value, int) - outbuff));

Redujo algunos tipeos repetitivos, pero debes tener cuidado de que no se vuelvan ilegibles.

Por favor, vea mi versión demasiado larga de esta respuesta here .


Lo he visto utilizado en macros donde la macro pretende ser una función y quiere devolver un valor, pero primero necesita hacer otro trabajo. Siempre es feo y a menudo parece un hack peligroso.

Ejemplo simplificado:

#define SomeMacro(A) ( DoWork(A), Permute(A) )

Aquí B=SomeMacro(A) "devuelve" el resultado de Permute (A) y lo asigna a "B".


Para mí, el caso realmente útil con comas en C es usarlos para realizar algo condicionalmente.

if (something) dothis(), dothat(), x++;

esto es equivalente a

if (something) { dothis(); dothat(); x++; }

No se trata de "escribir menos", a veces parece muy claro.

También los bucles son así:

while(true) x++, y += 5;

Por supuesto, ambos solo pueden ser útiles cuando la parte condicional o la parte ejecutable del ciclo es bastante pequeña, dos o tres operaciones.


Puede ser útil para "golf de código":

Code Golf: Jugar Cubos

El , en if(i>0)t=i,i=0; guarda dos personajes.


Puede sobrecargarlo (siempre que esta pregunta tenga una etiqueta "C ++"). He visto un código, donde la coma sobrecargada se utilizó para generar matrices. O vectores, no recuerdo exactamente. ¿No es bonito (aunque un poco confuso):

MyVector foo = 2, 3, 4, 5, 6;


Tuve que usar una coma para depurar bloqueos mutex para poner un mensaje antes de que el bloqueo comience a esperar.

No pude sino el mensaje de registro en el cuerpo del constructor de bloqueo derivado, así que tuve que ponerlo en los argumentos del constructor de la clase base usando: baseclass ((log ("message"), actual_arg)) en la lista de inicialización. Tenga en cuenta el paréntesis adicional.

Aquí hay un extracto de las clases:

class NamedMutex : public boost::timed_mutex { public: ... private: std::string name_ ; }; void log( NamedMutex & ref__ , std::string const& name__ ) { LOG( name__ << " waits for " << ref__.name_ ); } class NamedUniqueLock : public boost::unique_lock< NamedMutex > { public: NamedUniqueLock::NamedUniqueLock( NamedMutex & ref__ , std::string const& name__ , size_t const& nbmilliseconds ) : boost::unique_lock< NamedMutex >( ( log( ref__ , name__ ) , ref__ ) , boost::get_system_time() + boost::posix_time::milliseconds( nbmilliseconds ) ), ref_( ref__ ), name_( name__ ) { } .... };


qemu tiene un código que usa el operador de coma dentro de la porción condicional de un bucle for (ver QTAILQ_FOREACH_SAFE en qemu-queue.h). Lo que hicieron se reduce a lo siguiente:

#include <stdio.h> int main( int argc, char* argv[] ){ int x = 0, y = 0; for( x = 0; x < 3 && (y = x+1,1); x = y ){ printf( "%d, %d/n", x, y ); } printf( "/n%d, %d/n/n", x, y ); for( x = 0, y = x+1; x < 3; x = y, y = x+1 ){ printf( "%d, %d/n", x, y ); } printf( "/n%d, %d/n", x, y ); return 0; }

... con el siguiente resultado:

0, 1 1, 2 2, 3 3, 3 0, 1 1, 2 2, 3 3, 4

La primera versión de este ciclo tiene los siguientes efectos:

  • Evita hacer dos asignaciones, por lo que las posibilidades de que el código no se sincronice se reducen
  • Como usa && , la asignación no se evalúa después de la última iteración
  • Como la asignación no se evalúa, no intentará desviar el siguiente elemento de la cola cuando esté al final (en el código de qemu, no en el código anterior).
  • Dentro del ciclo, tienes acceso al elemento actual y siguiente