Come non consentire i provvisori

Per una class Foo, c’è un modo per non permetterlo di costruirlo senza dargli un nome?

Per esempio:

Foo("hi"); 

E permettilo solo se gli dai un nome, come il seguente?

 Foo my_foo("hi"); 

La durata del primo è solo l’affermazione, e il secondo è il blocco che lo racchiude. Nel mio caso d’uso, Foo sta misurando il tempo tra costruttore e distruttore. Dal momento che non mi riferisco mai alla variabile locale, mi dimentico spesso di inserirla e cambiare per sbaglio la durata. Mi piacerebbe ottenere un errore di compilazione invece.

Un’altra soluzione basata su macro:

 #define Foo class Foo 

La dichiarazione Foo("hi"); si espande in class Foo("hi"); che è mal formato; ma Foo a("hi") espande in class Foo a("hi") , che è corretto.

Ciò ha il vantaggio che è compatibile sia con la sorgente che con il codice binario con il codice (corretto) esistente. (Questa affermazione non è del tutto corretta – per favore vedi il commento di Johannes Schaub e la seguente discussione che segue: “Come puoi sapere che è compatibile sorgente con codice esistente? Il suo amico include la sua intestazione e ha void f () {int Foo = 0;} che in precedenza compilava bene e ora compiva errori! Inoltre, ogni riga che definisce una funzione membro della class Foo fallisce: void class Foo :: bar () {} “ )

Che ne dici di un piccolo trucco

 class Foo { public: Foo (const char*) {} }; void Foo (float); int main () { Foo ("hello"); // error class Foo a("hi"); // OK return 1; } 

Rendi privato il costruttore, ma dai alla class un metodo di creazione .

Questo non ha come risultato un errore del compilatore, ma un errore di runtime. Invece di misurare un orario sbagliato, si ottiene un’eccezione che può essere accettabile.

Qualsiasi costruttore che si desidera proteggere richiede un argomento predefinito su quale set(guard) viene chiamato.

 struct Guard { Guard() :guardflagp() { } ~Guard() { assert(guardflagp && "Forgot to call guard?"); *guardflagp = 0; } void *set(Guard const *&guardflag) { if(guardflagp) { *guardflagp = 0; } guardflagp = &guardflag; *guardflagp = this; } private: Guard const **guardflagp; }; class Foo { public: Foo(const char *arg1, Guard &&g = Guard()) :guard() { g.set(guard); } ~Foo() { assert(!guard && "A Foo object cannot be temporary!"); } private: mutable Guard const *guard; }; 

Le caratteristiche sono:

 Foo f() { // OK (no temporary) Foo f1("hello"); // may throw (may introduce a temporary on behalf of the compiler) Foo f2 = "hello"; // may throw (introduces a temporary that may be optimized away Foo f3 = Foo("hello"); // OK (no temporary) Foo f4{"hello"}; // OK (no temporary) Foo f = { "hello" }; // always throws Foo("hello"); // OK (normal copy) return f; // may throw (may introduce a temporary on behalf of the compiler) return "hello"; // OK (initialized temporary lives longer than its initializers) return { "hello" }; } int main() { // OK (it's f that created the temporary in its body) f(); // OK (normal copy) Foo g1(f()); // OK (normal copy) Foo g2 = f(); } 

Il caso di f2 , f3 e il ritorno di "hello" potrebbe non essere desiderato. Per impedire il lancio, puoi consentire che la fonte di una copia sia temporanea, reimpostando la guard per proteggerci ora invece della fonte della copia. Ora capisci anche perché abbiamo usato i puntatori qui sopra – ci consente di essere flessibili.

 class Foo { public: Foo(const char *arg1, Guard &&g = Guard()) :guard() { g.set(guard); } Foo(Foo &&other) :guard(other.guard) { if(guard) { guard->set(guard); } } Foo(const Foo& other) :guard(other.guard) { if(guard) { guard->set(guard); } } ~Foo() { assert(!guard && "A Foo object cannot be temporary!"); } private: mutable Guard const *guard; }; 

Le caratteristiche per f2 , f3 e per return "hello" ora sono sempre // OK .

Alcuni anni fa ho scritto una patch per il compilatore GNU C ++ che aggiunge una nuova opzione di avviso per quella situazione. Questo è tracciabile in un object Bugzilla .

Sfortunatamente, GCC Bugzilla è un luogo di sepoltura in cui i suggerimenti delle feature ben incluse nelle patch vanno bene. 🙂

Ciò era motivato dal desiderio di catturare esattamente il tipo di bug che sono object di questa domanda nel codice che utilizza oggetti locali come gadget per bloccare e sbloccare, misurare il tempo di esecuzione e così via.

Come è, con la tua implementazione, non puoi farlo, ma puoi usare questa regola a tuo vantaggio:

Gli oggetti temporanei non possono essere associati a riferimenti non const

È ansible spostare il codice dalla class a una funzione indipendente che accetta un parametro di riferimento non const. In tal caso, verrà visualizzato un errore del compilatore se un tentativo temporaneo tenta di collegarsi al riferimento non const.

Esempio di codice

 class Foo { public: Foo(const char* ){} friend void InitMethod(Foo& obj); }; void InitMethod(Foo& obj){} int main() { Foo myVar("InitMe"); InitMethod(myVar); //Works InitMethod("InitMe"); //Does not work return 0; } 

Produzione

 prog.cpp: In function 'int main()': prog.cpp:13: error: invalid initialization of non-const reference of type 'Foo&' from a temporary of type 'const char*' prog.cpp:7: error: in passing argument 1 of 'void InitMethod(Foo&)' 

Semplicemente non hai un costruttore predefinito e richiedi un riferimento a un’istanza in ogni costruttore.

 #include  using namespace std; enum SelfRef { selfRef }; struct S { S( SelfRef, S const & ) {} }; int main() { S a( selfRef, a ); } 

No, temo che non sia ansible. Ma potresti ottenere lo stesso effetto creando una macro.

 #define FOO(x) Foo _foo(x) 

Con questo al suo posto, puoi semplicemente scrivere FOO (x) invece di Foo my_foo (x).

Poiché l’objective principale è prevenire i bug, considera questo:

 struct Foo { Foo( const char* ) { /* ... */ } }; enum { Foo }; int main() { struct Foo foo( "hi" ); // OK struct Foo( "hi" ); // fail Foo foo( "hi" ); // fail Foo( "hi" ); // fail } 

In questo modo non puoi dimenticare di nominare la variabile e non puoi dimenticare di scrivere struct . Verboso, ma sicuro.

Dichiara un costruttore parametrico come esplicito e nessuno creerà mai un object di quella class involontariamente.

Per esempio

 class Foo { public: explicit Foo(const char*); }; void fun(const Foo&); 

può essere usato solo in questo modo

 void g() { Foo a("text"); fun(a); } 

ma mai in questo modo (attraverso un temporaneo in pila)

 void g() { fun("text"); } 

Vedi anche: Alexandrescu, Standard di codifica C ++, Articolo 40.