Si tengo dos variables de miembros constantes diferentes, que deben inicializarse en función de la misma llamada a la función, ¿hay alguna manera de hacerlo sin llamar a la función dos veces?
Por ejemplo, una clase de fracción donde el numerador y el denominador son constantes.
int gcd(int a, int b); // Greatest Common Divisor
class Fraction {
public:
// Lets say we want to initialize to a reduced fraction
Fraction(int a, int b) : numerator(a/gcd(a,b)), denominator(b/gcd(a,b))
{
}
private:
const int numerator, denominator;
};
Esto da como resultado una pérdida de tiempo, ya que la función GCD se llama dos veces. También podría definir un nuevo miembro de clase gcd_a_b
, y primero asignar la salida de gcd a eso en la lista de inicializadores, pero luego esto conduciría a un desperdicio de memoria.
En general, ¿hay alguna manera de hacer esto sin llamadas de función o memoria desperdiciadas? ¿Quizás puede crear variables temporales en una lista de inicializador? Gracias.
-O3
. Pero probablemente para cualquier implementación de prueba simple, en realidad se alinearía la llamada a la función. Si usa__attribute__((const))
o es puro en el prototipo sin proporcionar una definición visible, debería permitir que GCC o clang hagan la eliminación de subexpresión común (CSE) entre las dos llamadas con el mismo argumento. Tenga en cuenta que la respuesta de Drew funciona incluso para funciones no puras, por lo que es mucho mejor y debe usarla siempre que la función no esté en línea.Respuestas:
Si. Esto se puede hacer con un constructor delegante , introducido en C ++ 11.
Un constructor delegar es una forma muy eficiente de adquirir valores temporales necesarios para la construcción antes de cualquier se inicializan las variables miembro.
fuente
.h
), incluso si la definición de constructor real no es visible para la inserción. es decir, lagcd()
llamada se alinearía en cada sitio de llamada del constructor, y dejaría solocall
a al constructor privado de 3 operandos.Las variables miembro se inicializan por el orden en que se declaran en la declinación de la clase, por lo tanto, puede hacer lo siguiente (matemáticamente)
No es necesario llamar a otros constructores o incluso hacerlos.
fuente
Fraction(a,b,gcd(a,b))
delegación en la persona que llama, lo que lleva a un menor costo total. Esa alineación es más fácil para el compilador que deshacer la división adicional en esto. No lo probé en godbolt.org pero podrías hacerlo si tienes curiosidad. Use gcc o clang-O3
como lo haría una construcción normal. (C ++ está diseñado en torno a la suposición de un compilador de optimización moderno, por lo tanto, características comoconstexpr
)@Drew Dormann dio una solución similar a lo que tenía en mente. Como OP nunca menciona que no se puede modificar el ctor, esto se puede llamar con
Fraction f {a, b, gcd(a, b)}
:Solo de esta manera no hay una segunda llamada a una función, constructor o de otra manera, por lo que no se pierde tiempo. Y no es un desperdicio de memoria ya que de todos modos tendría que crearse un temporal, por lo que también puede aprovecharlo. También evita una división extra.
fuente
const
, pero al menos funciona para otros tipos. ¿Y qué división adicional estás "también" evitando? ¿Te refieres a la respuesta de asmmo?,gcd(foo, bar)
es un código extra que podría y, por lo tanto, debería factorizarse de cada sitio de llamadas en la fuente . Ese es un problema de mantenibilidad / legibilidad, no de rendimiento. Lo más probable es que el compilador lo incorpore en el momento de la compilación, que desea para el rendimiento.Fraction f( x+y, a+b );
Para escribirlo a su manera, tendría que escribirBadFraction f( x+y, a+b, gcd(x+y, a+b) );
o usar tmp vars. O peor aún, ¿qué pasa si desea escribirFraction f( foo(x), bar(y) );
? Entonces necesitaría que el sitio de la llamada declare algunos tmp vars para mantener los valores de retorno, o llame a esas funciones nuevamente y espere que el compilador las elimine, lo cual es lo que estamos evitando. ¿Desea depurar el caso de una persona que llama mezclando los argumentos paragcd
que en realidad no sea el MCD de los primeros 2 argumentos pasados al constructor? ¿No? Entonces no hagas posible ese error.