Ich habe eine Klasse wie diese:
struct event_counts {
uint64_t counts[MAX_COUNTERS];
event_counts() : counts{} {}
// more stuff
};
Normalerweise möchte ich das counts
Array standardmäßig (Null) wie gezeigt initialisieren .
An ausgewählten Stellen, die durch Profilerstellung identifiziert wurden, möchte ich jedoch die Array-Initialisierung unterdrücken, da ich weiß, dass das Array bald überschrieben wird, der Compiler jedoch nicht intelligent genug ist, um dies herauszufinden.
Was ist eine idiomatische und effiziente Methode, um einen solchen "sekundären" Null-Arg-Konstruktor zu erstellen?
Derzeit verwende ich eine Tag-Klasse, uninit_tag
die wie folgt als Dummy-Argument übergeben wird:
struct uninit_tag{};
struct event_counts {
uint64_t counts[MAX_COUNTERS];
event_counts() : counts{} {}
event_counts(uninit_tag) {}
// more stuff
};
Dann rufe ich den No-Init-Konstruktor auf, wie event_counts c(uninit_tag{});
wenn ich die Konstruktion unterdrücken möchte.
Ich bin offen für Lösungen, bei denen keine Dummy-Klasse erstellt wird oder die in irgendeiner Weise effizienter sind usw.
quelle
Antworten:
Die Lösung, die Sie bereits haben, ist korrekt und genau das, was ich sehen möchte, wenn ich Ihren Code überprüfe. Es ist so effizient wie möglich, klar und prägnant.
quelle
uninit_tag
an jedem Ort, an dem ich diese Redewendung verwenden möchte , einen neuen Geschmack deklarieren sollte . Ich hatte gehofft, dass es schon so etwas wie einen Indikatortyp gibt, vielleicht instd::
.no_init
Tag definieren und es in allen meinen Klassen verwenden, in denen es benötigt wird.std::piecewise_construct_t
undstd::in_place_t
. Keiner von ihnen scheint hier vernünftig zu sein. Vielleicht möchten Sie ein globales Objekt Ihres Typs definieren, das immer verwendet werden soll, damit Sie die Klammern nicht in jedem Konstruktoraufruf benötigen. Die STL macht das mitstd::piecewise_construct
fürstd::piecewise_construct_t
.Wenn der Konstruktorkörper leer ist, kann er weggelassen oder voreingestellt werden:
Dann Standardinitialisierung
event_counts counts;
wird verlassencounts.counts
nicht initialisierte (Standard Initialisierung ist ein No-op hier) und Wert Initialisierungevent_counts counts{};
initialize Wert wirdcounts.counts
, effektiv mit Nullen gefüllt wird .quelle
int i;
wir akzeptieren, dass es nicht nullinitialisiert ist. Vielleicht sollten wir auch akzeptieren, dass diesevent_counts counts;
nicht auf Null initialisiert ist, undevent_counts counts{};
unseren neuen Standard festlegen.Ich mag deine Lösung. Möglicherweise haben Sie auch verschachtelte Strukturen und statische Variablen berücksichtigt. Zum Beispiel:
Mit statischen Variablen scheint ein nicht initialisierter Konstruktoraufruf bequemer zu sein:
Sie können natürlich ein Makro einführen, um die Eingabe zu speichern und es systematischer zu gestalten
quelle
Ich denke, eine Aufzählung ist eine bessere Wahl als eine Tag-Klasse oder ein Bool. Sie müssen keine Instanz einer Struktur übergeben, und dem Aufrufer ist klar, welche Option Sie erhalten.
Das Erstellen von Instanzen sieht dann folgendermaßen aus:
Oder verwenden Sie anstelle der Tag-Klasse eine einwertige Aufzählung, um den Tag-Klassen-Ansatz zu verbessern:
Dann gibt es nur zwei Möglichkeiten, eine Instanz zu erstellen:
quelle
event_counts() : counts{} {}
counts
bedingungslos zu initialisieren , sondern nur wennINIT
gesetzt ist.bool
undenum
sind anständig, aber wir müssen uns bewusst sein, dass die Verwendung von Parametern anstelle von Überladung einen etwas anderen semantischen Farbton hat. Im ersten Fall parametrisieren Sie ein Objekt eindeutig, sodass die initialisierte / nicht initialisierte Haltung zu seinem Status wird, während das Übergeben eines Tag-Objekts an ctor eher der Aufforderung an die Klasse entspricht, eine Konvertierung durchzuführen. IMO ist also keine Frage der syntaktischen Wahl.event_counts() : counts{} {}
.counts
wird von initialisiert,std::fill
sofern nichtNO_INIT
angefordert. Wenn Sie den von Ihnen vorgeschlagenen Standardkonstruktor hinzufügen, können Sie die Standardinitialisierung auf zwei verschiedene Arten durchführen. Dies ist keine gute Idee. Ich habe einen anderen Ansatz hinzugefügt, der die Verwendung vermeidetstd::fill
.Möglicherweise möchten Sie eine zweiphasige Initialisierung für Ihre Klasse in Betracht ziehen :
Der obige Konstruktor initialisiert das Array nicht auf Null. Um die Elemente des Arrays auf Null zu setzen, müssen Sie die Elementfunktion
set_zero()
nach der Erstellung aufrufen .quelle
std::function
als Konstruktorargument mit etwas ähnlichem wieset_zero
als Standardargument versehen. Sie würden dann eine Lambda-Funktion übergeben, wenn Sie ein nicht initialisiertes Array möchten.Ich würde es so machen:
Der Compiler ist intelligent genug, um den gesamten Code zu überspringen, wenn Sie ihn verwenden
event_counts(false)
, und Sie können genau sagen, was Sie meinen, anstatt die Benutzeroberfläche Ihrer Klasse so seltsam zu machen.quelle
event_counts(false)
Was bedeutet das, wenn Sie mitlesen und die Erklärung sehen ? Sie haben keine Ahnung, ohne den Namen des Parameters zu überprüfen. Verwenden Sie mindestens eine Aufzählung oder in diesem Fall eine Sentinel / Tag-Klasse, wie in der Frage gezeigt. Dann erhalten Sie eine ähnliche Erklärungevent_counts(no_init)
, die für jeden in ihrer Bedeutung offensichtlich ist.event_counts(bool initCountr = true)
.boost::parameter
und aufrufenevent_counts(initCounts = false)
event_counts(bool initCounts = true)
eigentlich ist ein Default - Konstruktor, aufgrund jeder Parameter einen Standardwert. Die Anforderung ist nur, dass es ohne Angabe von Argumenten aufrufbar ist,event_counts ec;
sich nicht darum kümmert, ob es parameterlos ist oder Standardwerte verwendet.Ich würde eine Unterklasse verwenden, um ein bisschen Tipparbeit zu sparen:
Sie können die Dummy-Klasse entfernen, indem Sie das Argument des nicht initialisierenden Konstruktors in
bool
oderint
oder so ändern , da es nicht mehr mnemonisch sein muss.Sie können die Vererbung auch vertauschen und
events_count_no_init
mit einem Standardkonstruktor definieren, wie er in der Antwort von Evg vorgeschlagen wurde, und dannevents_count
die Unterklasse sein:quelle
event_counts
, möchte ich, dass sie vom Typ istevent_count
, nicht vom Typ ,event_count_uninitialized
also sollte ich direkt bei der Konstruktion abschneidenevent_counts c = event_counts_no_init{};
, was meiner Meinung nach die meisten Einsparungen beim Tippen eliminiert.event_count_uninitialized
Objekt einevent_count
Objekt. Das ist der springende Punkt bei der Vererbung, es sind keine völlig unterschiedlichen Typen.ecu
zuec
es funktioniert, aber nicht umgekehrt. Oder wenn Sie Vorlagenfunktionen verwenden, handelt es sich um unterschiedliche Typen, die mit unterschiedlichen Instanziierungen enden, selbst wenn das Verhalten identisch ist (und manchmal nicht, z. B. bei statischen Vorlagenelementen). Besonders bei starker Beanspruchungauto
kann dies definitiv auftauchen und verwirrend sein: Ich möchte nicht, dass sich die Art und Weise, wie ein Objekt initialisiert wurde, dauerhaft in seinem Typ widerspiegelt.