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 counts
matriz 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 forma idiomática y eficiente de crear un constructor de "cero argumentos" 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.
fuente
Respuestas:
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.
fuente
uninit_tag
sabor en cada lugar en el que quiero usar este idioma. Esperaba que ya hubiera algo así como un tipo de indicador, tal vez enstd::
.no_init
y la usaría en todas mis clases donde sea necesaria.std::piecewise_construct_t
ystd::in_place_t
. Ninguno de ellos parece razonable de usar aquí. Tal vez le gustaría definir un objeto global de su tipo para usar siempre, por lo que no necesita las llaves en cada llamada de constructor. El STL hace esto constd::piecewise_construct
parastd::piecewise_construct_t
.Si el cuerpo del constructor está vacío, se puede omitir o omitir:
Luego, la inicialización predeterminada
event_counts counts;
dejará sincounts.counts
inicializar (la inicialización predeterminada no es operativa aquí), y la inicialización deevent_counts counts{};
valor valorará la inicializacióncounts.counts
, llenándola efectivamente con ceros.fuente
int i;
aceptamos que no tiene inicialización cero. Tal vez también deberíamos aceptar queevent_counts counts;
no está inicializado en cero y hacerevent_counts counts{};
nuestro nuevo valor predeterminado.Me gusta tu solución También podría haber considerado la estructura anidada y la variable estática. Por ejemplo:
Con la variable estática, la llamada del constructor no inicializado puede parecer más conveniente:
Por supuesto, puede introducir una macro para guardar la escritura y convertirla en una característica más sistemática
fuente
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.
Luego, crear instancias se ve así:
O, para que se parezca más al enfoque de la clase de etiqueta, use una enumeración de un solo valor en lugar de la clase de etiqueta:
Entonces solo hay dos formas de crear una instancia:
fuente
event_counts() : counts{} {}
counts
incondicionalmente, sino solo cuandoINIT
está configurado.bool
yenum
son decentes, pero debemos ser conscientes de que el uso de parámetros en lugar de sobrecarga tiene un tono semántico algo diferente. En el primero, claramente parametriza un objeto, por lo tanto, la posición inicializada / no inicializada se convierte en su estado, mientras que pasar un objeto de etiqueta a ctor es más como pedirle a la clase que realice una conversión. Por lo tanto, no es IMO una cuestión de elección sintáctica.event_counts() : counts{} {}
.counts
se inicializa astd::fill
menos queNO_INIT
se solicite. Agregar el constructor predeterminado como usted sugiere haría dos formas diferentes de hacer la inicialización predeterminada, lo cual no es una gran idea. He agregado otro enfoque que evita el usostd::fill
.Es posible que desee considerar una inicialización de dos fases para su clase:
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.fuente
std::function
como argumento de constructor con algo similar alset_zero
argumento predeterminado. Luego pasaría una función lambda si desea una matriz no inicializada.Lo haría así:
El compilador será lo suficientemente inteligente como para omitir todo el código cuando lo 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.fuente
event_counts(false)
, ¿qué significa eso? No tiene idea sin volver atrás y mirar el nombre del parámetro. Es mejor al menos usar una enumeración o, en este caso, una clase centinela / etiqueta como se muestra en la pregunta. Luego, obtienes una declaración más comoevent_counts(no_init)
, que es obvio para todos en su significado.event_counts(bool initCountr = true)
.boost::parameter
y solicitarevent_counts(initCounts = false)
legibilidadevent_counts(bool initCounts = true)
realidad es un constructor predeterminado, debido a que cada parámetro tiene un valor predeterminado. El requisito es que sea invocable sin especificar argumentos,event_counts ec;
no le importa si no tiene parámetros o si usa valores predeterminados.Usaría una subclase solo para ahorrar un poco de escritura:
Usted puede deshacerse de la clase simulado por el cambio del argumento del constructor no inicializar a
bool
oint
o algo así, ya que no tiene por qué ser mnemotécnica más.También podría intercambiar la herencia y definirla
events_count_no_init
con un constructor predeterminado como sugirió Evg en su respuesta, y luegoevents_count
ser la subclase:fuente
event_counts
, querré que sea de tipoevent_count
, noevent_count_uninitialized
, así que debería cortar directamente en la construcciónevent_counts c = event_counts_no_init{};
, lo que creo que elimina la mayoría de los ahorros en escribir.event_count_uninitialized
objeto es unevent_count
objeto. Ese es el punto de herencia, no son tipos completamente diferentes.ecu
aec
funciona, pero no el otro alrededor de camino. O si usa funciones de plantilla, son de diferentes tipos y terminan con diferentes instancias, incluso si el comportamiento termina siendo idéntico (y a veces no lo será, por ejemplo, con miembros de plantilla estáticos). Especialmente con el uso intensivo deauto
esto definitivamente puede surgir y ser confuso: no quisiera que la forma en que se inicializó un objeto se refleje permanentemente en su tipo.