J'ai une classe comme ça:
struct event_counts {
uint64_t counts[MAX_COUNTERS];
event_counts() : counts{} {}
// more stuff
};
Habituellement, je veux initialiser par défaut (zéro) le counts
tableau comme indiqué.
À des emplacements sélectionnés identifiés par le profilage, cependant, je voudrais supprimer l'initialisation du tableau, car je sais que le tableau est sur le point d'être écrasé, mais le compilateur n'est pas assez intelligent pour le comprendre.
Quelle est une manière idiomatique et efficace de créer un constructeur zéro-arg «secondaire»?
Actuellement, j'utilise une classe de balises uninit_tag
qui est passée comme argument factice, comme ceci:
struct uninit_tag{};
struct event_counts {
uint64_t counts[MAX_COUNTERS];
event_counts() : counts{} {}
event_counts(uninit_tag) {}
// more stuff
};
Ensuite, j'appelle le constructeur no-init comme event_counts c(uninit_tag{});
quand je veux supprimer la construction.
Je suis ouvert aux solutions qui n'impliquent pas la création d'une classe factice, ou qui sont plus efficaces d'une manière ou d'une autre, etc.
la source
Réponses:
La solution que vous avez déjà est correcte, et c'est exactement ce que je voudrais voir si je révisais votre code. Il est aussi efficace que possible, clair et concis.
la source
uninit_tag
saveur à chaque endroit où je veux utiliser cet idiome. J'espérais qu'il y avait déjà quelque chose comme un tel type d'indicateur, peut-être dansstd::
.no_init
balise à l' échelle du projet et l'utiliserais dans toutes mes classes là où c'est nécessaire.std::piecewise_construct_t
etstd::in_place_t
. Aucun d'entre eux ne semble raisonnable à utiliser ici. Vous souhaitez peut-être définir un objet global de votre type à utiliser toujours, de sorte que vous n'ayez pas besoin des accolades dans chaque appel de constructeur. La STL fait cela avecstd::piecewise_construct
pourstd::piecewise_construct_t
.Si le corps du constructeur est vide, il peut être omis ou par défaut:
Ensuite, l' initialisation par défaut
event_counts counts;
ne sera pascounts.counts
initialisée (l'initialisation par défaut est un no-op ici), et l' initialisation de laevent_counts counts{};
valeur donnera la valeur initializecounts.counts
, la remplissant efficacement avec des zéros.la source
int i;
nous acceptons qu'il n'est pas initialisé à zéro. Peut-être devrions-nous également accepter que ceevent_counts counts;
ne soit pas initialisé à zéro et définirevent_counts counts{};
notre nouveau paramètre par défaut.J'aime ta solution. Vous pourriez également avoir considéré la structure imbriquée et la variable statique. Par exemple:
Avec une variable statique, l'appel du constructeur non initialisé peut sembler plus pratique:
Vous pouvez bien sûr introduire une macro pour enregistrer la saisie et en faire une fonctionnalité plus systématique
la source
Je pense qu'une énumération est un meilleur choix qu'une classe de balises ou un booléen. Vous n'avez pas besoin de passer une instance d'une structure et il est clair pour l'appelant quelle option vous obtenez.
Ensuite, la création d'instances ressemble à ceci:
Ou, pour que cela ressemble davantage à l'approche de classe de balises, utilisez une énumération à valeur unique au lieu de la classe de balises:
Il n'y a alors que deux façons de créer une instance:
la source
event_counts() : counts{} {}
counts
inconditionnellement, mais seulement quandINIT
est défini.bool
etenum
sont décents, mais nous devons être conscients que l'utilisation de paramètres au lieu de la surcharge a une nuance sémantique quelque peu différente. Dans le premier, vous paramétrez clairement un objet, donc la position initialisée / non initialisée devient son état, tandis que passer un objet tag à ctor revient plus à demander à la classe d'effectuer une conversion. Ce n'est donc pas une question de choix syntaxique de l'OMI.event_counts() : counts{} {}
.counts
est initialisée parstd::fill
sauf siNO_INIT
est demandé. L'ajout du constructeur par défaut comme vous le suggérez ferait deux façons différentes de faire l'initialisation par défaut, ce qui n'est pas une bonne idée. J'ai ajouté une autre approche qui évite d'utiliserstd::fill
.Vous voudrez peut-être envisager une initialisation en deux phases pour votre classe:
Le constructeur ci-dessus n'initialise pas le tableau à zéro. Pour mettre les éléments du tableau à zéro, vous devez appeler la fonction membre
set_zero()
après la construction.la source
std::function
un argument constructeur avec quelque chose de similaire à l'set_zero
argument par défaut. Vous passeriez alors une fonction lambda si vous voulez un tableau non initialisé.Je le ferais comme ça:
Le compilateur sera suffisamment intelligent pour ignorer tout le code lorsque vous utilisez
event_counts(false)
, et vous pouvez dire exactement ce que vous voulez dire au lieu de rendre l'interface de votre classe si bizarre.la source
event_counts(false)
, qu'est-ce que cela signifie? Vous n'avez aucune idée sans revenir en arrière et regarder le nom du paramètre. Mieux vaut au moins utiliser une énumération ou, dans ce cas, une classe sentinelle / tag comme indiqué dans la question. Ensuite, vous obtenez une déclaration plus semblable àevent_counts(no_init)
, ce qui est évident pour tout le monde dans son sens.event_counts(bool initCountr = true)
.boost::parameter
et appelerevent_counts(initCounts = false)
pour plus de lisibilitéevent_counts(bool initCounts = true)
en fait un constructeur par défaut, car chaque paramètre a une valeur par défaut. L'exigence est juste qu'il puisse être appelé sans spécifier d'arguments, peu importe s'il est sansevent_counts ec;
paramètre ou utilise des valeurs par défaut.J'utiliserais une sous-classe juste pour économiser un peu de frappe:
Vous pouvez vous débarrasser de la classe factice en changeant l'argument du constructeur non initialisé en
bool
ouint
ou quelque chose, car il n'a plus besoin d'être mnémonique.Vous pouvez également échanger l'héritage et définir
events_count_no_init
avec un constructeur par défaut comme Evg suggéré dans leur réponse, puis avoirevents_count
la sous-classe:la source
event_counts
, je veux qu'il soit de typeevent_count
, nonevent_count_uninitialized
, donc je devrais couper à la construction commeevent_counts c = event_counts_no_init{};
, ce qui, je pense, élimine la plupart des économies de frappe.event_count_uninitialized
objet est unevent_count
objet. C'est tout l'héritage, ce ne sont pas des types complètement différents.ecu
àec
cela fonctionne, mais pas l'inverse. Ou si vous utilisez des fonctions de modèle, elles sont de types différents et se terminent par des instanciations différentes même si le comportement finit par être identique (et parfois ce ne sera pas le cas, par exemple, avec des membres de modèle statiques). Surtout avec une utilisation intensive deauto
cela peut certainement surgir et être source de confusion: je ne voudrais pas que la façon dont un objet a été initialisé soit reflétée en permanence dans son type.