c++ performance constructor

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{} {} };