c++ - Manera idiomática de distinguir dos constructores de cero argumentos
performance (7)
Tengo una clase como esta:
struct event_counts {
uint64_t counts[MAX_COUNTERS];
event_counts() : counts{} {}
// more stuff
};
Por lo general, quiero predeterminar (cero) inicializar la matriz de
counts
como se muestra.
Sin embargo, en las ubicaciones seleccionadas identificadas por la creación de perfiles, me gustaría suprimir la inicialización de la matriz, porque sé que la matriz está a punto de sobrescribirse, pero el compilador no es lo suficientemente inteligente como para descubrirlo.
¿Cuál es una manera idiomática y eficiente de crear un constructor de "cero argumentos" tan secundario?
Actualmente, estoy usando una clase de etiqueta
uninit_tag
que se pasa como un argumento ficticio, así:
struct uninit_tag{};
struct event_counts {
uint64_t counts[MAX_COUNTERS];
event_counts() : counts{} {}
event_counts(uninit_tag) {}
// more stuff
};
Luego llamo al constructor no-init como
event_counts c(uninit_tag{});
cuando quiero suprimir la construcción
Estoy abierto a soluciones que no implican la creación de una clase ficticia, o que son más eficientes de alguna manera, etc.
Creo que una enumeración es una mejor opción que una clase de etiqueta o un bool. No necesita pasar una instancia de una estructura y la persona que llama sabe claramente qué opción está obteniendo.
struct event_counts {
enum Init { INIT, NO_INIT };
uint64_t counts[MAX_COUNTERS];
event_counts(Init init = INIT) {
if (init == INIT) {
std::fill(counts, counts + MAX_COUNTERS, 0);
}
}
};
Luego, crear instancias se ve así:
event_counts e1{};
event_counts e2{event_counts::INIT};
event_counts e3{event_counts::NO_INIT};
Es posible que desee considerar una inicialización de dos fases para su clase:
struct event_counts {
uint64_t counts[MAX_COUNTERS];
event_counts() = default;
void set_zero() {
std::fill(std::begin(counts), std::end(counts), 0u);
}
};
El constructor anterior no inicializa la matriz a cero.
Para establecer los elementos de la matriz en cero, debe llamar a la función miembro
set_zero()
después de la construcción.
La solución que ya tiene es correcta, y es exactamente lo que me gustaría ver si estuviera revisando su código. Es lo más eficiente posible, claro y conciso.
Lo haría así:
struct event_counts {
uint64_t counts[MAX_COUNTERS];
event_counts() : counts{} {}
event_counts(bool initCounts) {
if (initCounts) {
std::fill(counts, counts + MAX_COUNTERS, 0);
}
}
};
El compilador será lo suficientemente inteligente como para omitir todo el código cuando use
event_counts(false)
, y podrá decir exactamente lo que quiere decir en lugar de hacer que la interfaz de su clase sea tan extraña.
Me gusta tu solución También podría haber considerado la estructura anidada y la variable estática. Por ejemplo:
struct event_counts {
static constexpr struct uninit_tag {} uninit = uninit_tag();
uint64_t counts[MAX_COUNTS];
event_counts() : counts{} {}
explicit event_counts(uninit_tag) {}
// more stuff
};
Con la variable estática, la llamada del constructor no inicializado puede parecer más conveniente:
event_counts e(event_counts::uninit);
Por supuesto, puede introducir una macro para guardar la escritura y convertirla en una característica más sistemática
#define UNINIT_TAG static constexpr struct uninit_tag {} uninit = uninit_tag();
struct event_counts {
UNINIT_TAG
}
struct other_counts {
UNINIT_TAG
}
Si el cuerpo del constructor está vacío, se puede omitir o omitir:
struct event_counts {
std::uint64_t counts[MAX_COUNTERS];
event_counts() = default;
};
Luego, la
inicialización predeterminada
event_counts counts;
dejará
counts.counts
inicializar (la inicialización predeterminada no es
counts.counts
aquí) y la
inicialización de valor
event_counts counts{};
valorará inicializar
counts.counts
, llenándolo efectivamente con ceros.
Usaría una subclase solo para ahorrar un poco de escritura:
struct event_counts {
uint64_t counts[MAX_COUNTERS];
event_counts() : counts{} {}
event_counts(uninit_tag) {}
};
struct event_counts_no_init: event_counts {
event_counts_no_init(): event_counts(uninit_tag{}) {}
};
Puede deshacerse de la clase ficticia cambiando el argumento del constructor que no se inicializa a
bool
o
int
o algo así, ya que ya no tiene que ser mnemónico.
También puede intercambiar la herencia y definir
events_count_no_init
con un constructor predeterminado como Evg sugirió en su respuesta, y luego hacer que
events_count
sea la subclase:
struct event_counts_no_init {
uint64_t counts[MAX_COUNTERS];
event_counts_no_init() = default;
};
struct event_counts: event_counts_no_init {
event_counts(): event_counts_no_init{} {}
};