Domanda

Dove lavoro, la maggior parte delle persone pensa che gli oggetti siano inizializzati al meglio usando una costruzione in stile C ++ (con parentesi), mentre i tipi primitivi dovrebbero essere inizializzati con l'operatore =

std::string strFoo( "Foo" );
int nBar = 5;

Nessuno sembra essere in grado di spiegare perché preferiscono le cose in questo modo, però. Vedo che std::string = "Foo"; sarebbe inefficiente perché implicherebbe una copia aggiuntiva, ma cosa c'è di sbagliato nel bandire completamente l'operatore = e usare le parentesi ovunque?

È una convenzione comune? Qual è il pensiero dietro?

È stato utile?

Soluzione

A meno che tu non abbia dimostrato che è importante per quanto riguarda le prestazioni, non mi preoccuperei di una copia aggiuntiva usando l'operatore di assegnazione nel tuo esempio (std::string foo = "Foo";). Sarei piuttosto sorpreso se quella copia esistesse anche dopo aver esaminato il codice ottimizzato, credo che chiamerà effettivamente il costruttore con parametri appropriato.

In risposta alla tua domanda, sì, direi che è una convenzione abbastanza comune. Classicamente, le persone hanno usato il compito per inizializzare i tipi predefiniti e non c'è un motivo convincente per cambiare la tradizione. La leggibilità e l'abitudine sono ragioni perfettamente valide per questa convenzione dato il minimo impatto che ha sul codice definitivo.

Altri suggerimenti

L'inizializzazione delle variabili con l'operatore = o con una chiamata del costruttore è semanticamente la stessa, è solo una questione di stile. Preferisco l'operatore =, poiché legge in modo più naturale.

L'uso dell'operatore = di solito non genera una copia aggiuntiva, ma chiama semplicemente il costruttore normale. Si noti, tuttavia, che con tipi non primitivi, questo è solo per le inizializzazioni che si verificano contemporaneamente alle dichiarazioni. Confronto:

std::string strFooA("Foo");  // Calls std::string(const char*) constructor
std::string strFoo = "Foo";  // Calls std::string(const char*) constructor
                             // This is a valid (and standard) compiler optimization.

std::string strFoo;  // Calls std::string() default constructor
strFoo = "Foo";      // Calls std::string::operator = (const char*)

Quando si hanno costruttori predefiniti non banali, quest'ultima costruzione può essere leggermente più inefficiente.

Standard C ++ , sezione 8.5 , il paragrafo 14 afferma:

  

Altrimenti (vale a dire, per i restanti casi di inizializzazione della copia), viene creato un temporaneo. Vengono elencate le sequenze di conversione definite dall'utente che possono convertire dal tipo di origine al tipo di destinazione o una classe derivata (13.3.1.4) e la migliore viene scelta tramite la risoluzione di sovraccarico (13.3). La conversione definita dall'utente così selezionata viene chiamata per convertire l'espressione dell'inizializzatore in un temporaneo, il cui tipo è il tipo restituito dalla chiamata della funzione di conversione definita dall'utente, con i qualificatori cv   del tipo di destinazione. Se la conversione non può essere eseguita o è ambigua, l'inizializzazione non è corretta. L'oggetto che viene inizializzato viene quindi inizializzato direttamente   dal temporaneo in base alle regole precedenti. 87 ) In alcuni casi, è consentita un'implementazione per eliminare il temporaneo inizializzando direttamente l'oggetto; vedi 12.2.

Parte della sezione 12.2 afferma:

  

Anche quando si evita la creazione dell'oggetto temporaneo, tutte le restrizioni semantiche devono essere rispettate come se fosse stato creato l'oggetto temporaneo. [Esempio:   anche se il costruttore della copia non viene chiamato, devono essere soddisfatte tutte le restrizioni semantiche, come l'accessibilità (11). ]

Ho appena sentito la necessità di un altro stupido post litb.

string str1 = "foo";

si chiama inizializzazione della copia , perché ciò che fa il compilatore, se non elude alcun temporaneo, è:

string str1(string("foo")); 

oltre a verificare che il costruttore di conversione utilizzato sia implicito. In effetti, tutte le conversioni implicite sono definite dallo standard in termini di inizializzazione della copia. Si dice che una conversione implicita dal tipo U al tipo T sia valida, se

T t = u; // u of type U

è valido.

In constrast,

string str1("foo");

sta facendo esattamente ciò che è scritto e si chiama inizializzazione diretta . Funziona anche con costruttori espliciti.

A proposito, puoi disabilitare l'elezione dei provvisori usando -fno-elide-constructors:

-fno-elide-constructors
    The C++ standard allows an implementation to omit creating a temporary which 
    is only used to initialize another object of the same type. Specifying this 
    option disables that optimization, and forces G++ to call the copy constructor 
    in all cases.

Lo standard dice che non c'è praticamente alcuna differenza tra

T a = u;

e

T a(u);

se T e il tipo di u sono tipi primitivi. Quindi puoi usare entrambi i moduli. Penso che sia solo il suo stile a indurre le persone a usare la prima forma piuttosto che la seconda.


Alcune persone potrebbero usare la prima in alcune situazioni, perché vogliono chiarire la dichiarazione:

T u(v(a));

potrebbe guardare a qualcuno come una definizione di una variabile u che è inizializzata usando un temporaneo di tipo v che ottiene un parametro per il suo costruttore chiamato a. Ma in effetti, ciò che fa il compilatore è questo:

T u(v a);

Crea una dichiarazione di funzione che accetta un argomento di tipo <=> e con un parametro chiamato <=>. Quindi la gente lo fa

T u = v(a);

per chiarire ciò, anche se avrebbero potuto farlo

T u((v(a)));

anche perché non ci sono mai parentesi attorno ai parametri della funzione, il compilatore lo leggerebbe come una definizione di variabile invece che come una dichiarazione di funzione :)

Probabilmente troverai quel codice come

std::string strFoo = "Foo";

eviterà di fare una copia aggiuntiva e si compila nello stesso codice (una chiamata di un costruttore a argomento singolo) di quello tra parentesi.

D'altra parte, ci sono casi in cui un deve usare parentesi, come un elenco di inizializzazione dei membri del costruttore.

Penso che l'uso di = o parentesi per costruire variabili locali sia in gran parte una scelta personale.

Beh, chissà cosa pensano loro , ma preferisco anche il = per i tipi primitivi, principalmente perché non sono oggetti e perché è la " solita " modo per inizializzarli.

È un problema di stile. Perfino l'affermazione che & Quot; std :: string = & Quot; Foo & Quot ;; sarebbe inefficiente perché implicherebbe una copia extra " non è corretto. Questo & Quot; copia extra & Quot; viene rimosso dal compilatore.

Credo che sia più un'abitudine, pochissimi oggetti potrebbero essere inizializzati usando =, la stringa è una di queste. È anche un modo di fare ciò che hai detto & Quot; usare la parentesi ovunque (che la lingua ti consente di usarlo) & Quot;

Un argomento che si potrebbe sostenere:

std :: string foo (" bar ");

È che mantiene le cose uguali anche se cambia il conteggio degli argomenti, cioè:

std :: string foo (" bar " ;, 5);

Non funziona con un segno '='.

Un'altra cosa è che per molti oggetti un '=' sembra innaturale, ad esempio diciamo che hai una classe Array in cui l'argomento fornisce la lunghezza:

Array arr = 5;

Non si sente bene, dal momento che non costruiamo un array con il valore 5, ma con lunghezza 5:

Array arr (5);

sembra più naturale, poiché stai costruendo un oggetto con il parametro dato, non solo copiando un valore.

Ma solo per confondervi ancora di più inizializzate le primitive nell'elenco di inizializzazione usando la sintassi degli oggetti.

foo::foo()   
  ,anInt(0)   
  ,aFloat(0.0)   
{   
}   
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top