No permitir la creación de los objetos temporales

Mientras se depuraba la falla en una aplicación multiproceso, finalmente localicé el problema en esta statement:

CSingleLock(&m_criticalSection, TRUE); 

Observe que está creando un objeto sin nombre de la clase CSingleLock y, por lo tanto, el objeto de la sección crítica se desbloquea inmediatamente después de esta statement. Esto obviamente no es lo que el codificador quería. Este error fue causado por un simple error de tipeo. Mi pregunta es, ¿de alguna manera puedo evitar que el objeto temporal de una clase se cree en el tiempo de comstackción, es decir, el tipo de código anterior debería generar un error de comstackción. En general, creo que cada vez que una clase intenta hacer algún tipo de adquisición de recursos, entonces el objeto temporal de esa clase no debe permitirse. ¿Hay alguna forma de hacer cumplir?

Editar: como señala j_random_hacker, es posible forzar al usuario a declarar un objeto con nombre para sacar un locking.

Sin embargo, incluso si la creación de temporarios fuera de alguna manera prohibida para su clase, entonces el usuario podría cometer un error similar:

 // take out a lock: if (m_multiThreaded) { CSingleLock c(&m_criticalSection, TRUE); } // do other stuff, assuming lock is held 

En última instancia, el usuario debe comprender el impacto de una línea de código que escriben. En este caso, deben saber que están creando un objeto y deben saber cuánto tiempo dura.

Otro posible error:

  CSingleLock *c = new CSingleLock(&m_criticalSection, TRUE); // do other stuff, don't call delete on c... 

Lo que lo llevaría a preguntar “¿Hay alguna manera de evitar que el usuario de mi clase lo asigne en el montón”? A lo que la respuesta sería la misma.

En C ++ 0x habrá otra forma de hacer todo esto, usando lambdas. Definir una función:

 template  void WithLock(TLock *lock, const TLockedOperation &op) { CSingleLock c(lock, TRUE); op(); } 

Esa función captura el uso correcto de CSingleLock. Ahora permite que los usuarios hagan esto:

 WithLock(&m_criticalSection, [&] { // do stuff, lock is held in this context. }); 

Esto es mucho más difícil de perder para el usuario. La syntax se ve rara al principio, pero [&] seguido de un bloque de código significa “Definir una función que no toma argumentos, y si me refiero a cualquier cosa por nombre y es el nombre de algo externo (por ejemplo, una variable local en el función) déjame acceder a él por referencia no const, por lo que puedo modificarlo.)

En primer lugar, Earwicker hace algunos buenos comentarios : no se puede evitar cada uso indebido accidental de esta construcción.

Pero para su caso específico, esto de hecho puede evitarse. Esto se debe a que C ++ hace una distinción (extraña) con respecto a los objetos temporales: las funciones gratuitas no pueden tomar referencias no constantes a los objetos temporales. Por lo tanto, para evitar lockings que entren o salgan de la existencia, solo mueva el código de locking fuera del constructor CSingleLock y en una función libre (que puede hacer un amigo para evitar exponer internos como métodos):

 class CSingleLock { friend void Lock(CSingleLock& lock) { // Perform the actual locking here. } }; 

El deslocking aún se realiza en el destructor.

Usar:

 CSingleLock myLock(&m_criticalSection, TRUE); Lock(myLock); 

Sí, es un poco más difícil de escribir. Pero ahora, el comstackdor se quejará si intentas:

 Lock(CSingleLock(&m_criticalSection, TRUE)); // Error! Caught at compile time. 

Porque el parámetro ref no constante de Lock() no se puede unir a un temporal.

Quizás sorprendentemente, los métodos de clase pueden operar en temporarios, es por eso que Lock() necesita ser una función gratuita. Si suelta el especificador de friend y el parámetro de función en el fragmento superior para hacer que Lock() un método, entonces el comstackdor le permitirá escribir:

 CSingleLock(&m_criticalSection, TRUE).Lock(); // Yikes! 

NOTA DEL COMPILADOR DE MS: Las versiones de MSVC ++ hasta Visual Studio .NET 2003 permitieron incorrectamente que las funciones se enlazaran a referencias no const en versiones anteriores a VC ++ 2005. Este comportamiento se ha corregido en VC ++ 2005 y posteriores .

No, no hay forma de hacer esto. Si lo hace, romperá casi todo el código C ++, que depende en gran medida de crear temporales anónimos. Su única solución para clases específicas es hacer que sus constructores sean privados y luego siempre construirlos a través de algún tipo de fábrica. ¡Pero creo que la cura es peor que la enfermedad!

No lo creo.

Si bien no es algo sensato, como descubriste con tu error, no hay nada “ilegal” en la statement. El comstackdor no tiene forma de saber si el valor de retorno del método es “vital” o no.

El comstackdor no debe rechazar la creación temporal de objetos, en mi humilde opinión.

En casos especiales, como encoger un vector, realmente se necesita crear un objeto temporal.

 std::vector(v).swap(v); 

Aunque es un poco difícil, la revisión del código y las pruebas unitarias deben detectar estos problemas.

De lo contrario, aquí hay una solución para un hombre pobre:

 CSingleLock aLock(&m_criticalSection); //Don't use the second parameter whose default is FALSE aLock.Lock(); //an explicit lock should take care of your problem 

¿Qué hay de lo siguiente? Abusa ligeramente del preprocesador, pero es lo suficientemente inteligente como para pensar que debería incluirse:

 class CSingleLock { ... }; #define CSingleLock class CSingleLock 

Ahora olvida nombrar los resultados temporales en un error, porque mientras el siguiente es válido C ++:

 class CSingleLock lock(&m_criticalSection, true); // Compiles just fine! 

El mismo código, pero omitiendo el nombre, no es:

 class CSingleLock(&m_criticalSection, true); // <-- ERROR! 

Veo que en 5 años nadie ha encontrado la solución más simple:

 #define LOCK(x) CSingleLock lock(&x, TRUE); ... void f() { LOCK(m_criticalSection); 

Y ahora solo use esta macro para crear lockings. ¡Ninguna posibilidad de crear temporales más! Esto tiene la ventaja añadida de que la macro se puede boost fácilmente para realizar cualquier tipo de comprobación en comstackciones de depuración, por ejemplo, detectar lockings recursivos inapropiados, registrar archivos y líneas del locking, y mucho más.