Chiamare i costruttori in c ++ senza nuovi


142

Ho visto spesso che le persone creano oggetti in C ++ usando

Thing myThing("asdf");

Invece di questo:

Thing myThing = Thing("asdf");

Questo sembra funzionare (usando gcc), almeno finché non ci sono modelli coinvolti. La mia domanda ora, la prima riga è corretta e in tal caso dovrei usarla?


25
Entrambe le forme sono senza novità.
Daniel Daranas,

13
Il secondo modulo utilizzerà il costruttore di copie, quindi no, non sono equivalenti.
Edward Strange,

Ci ho giocato un po ', il primo modo sembra fallire a volte quando i template sono usati con costruttori senza parametri ..
Nils

1
Ouh e io abbiamo ottenuto il badge "Bella domanda" per quello, che peccato!
Nils,

Risposte:


153

Entrambe le linee sono in realtà corrette ma fanno cose leggermente diverse.

La prima riga crea un nuovo oggetto nello stack chiamando un costruttore del formato Thing(const char*).

Il secondo è un po 'più complesso. Sostanzialmente fa quanto segue

  1. Crea un oggetto di tipo Thingusando il costruttoreThing(const char*)
  2. Crea un oggetto di tipo Thingusando il costruttoreThing(const Thing&)
  3. Chiamare ~Thing()l'oggetto creato nel passaggio # 1

7
Immagino che questi tipi di azioni siano ottimizzati e quindi non differiscano in modo significativo in termini di prestazioni.
M. Williams,

14
Non penso che i tuoi passi siano giusti. Thing myThing = Thing(...)non utilizza l'operatore di assegnazione, è ancora costruito in copia proprio come dire Thing myThing(Thing(...)), e non implica un predefinito Thing(modifica: post è stato successivamente corretto)
AshleysBrain

1
Quindi puoi dire che la seconda riga non è corretta, perché spreca risorse senza una ragione apparente. Naturalmente è possibile che la creazione della prima istanza sia intenzionale per alcuni effetti collaterali, ma è anche peggio (stilisticamente).
MK.

3
No, @Jared, non è garantito. Ma anche se il compilatore sceglie di eseguire tale ottimizzazione, il costruttore di copie deve comunque essere accessibile (cioè, non protetto o privato), anche se non è implementato o chiamato.
Rob Kennedy,

3
Sembra che la copia possa essere elusa anche se il costruttore di copie ha effetti collaterali - vedi la mia risposta: stackoverflow.com/questions/2722879/…
Douglas Leeder

31

Presumo con la seconda riga che intendi davvero:

Thing *thing = new Thing("uiae");

quale sarebbe il modo standard di creare nuovi oggetti dinamici (necessari per il legame dinamico e polimorfismo) e memorizzare il loro indirizzo in un puntatore. Il tuo codice fa ciò che JaredPar ha descritto, cioè creando due oggetti (uno ha passato a const char*, l'altro ha passato a const Thing&) e quindi chiamando il distruttore ( ~Thing()) sul primo oggettoconst char* uno).

Al contrario, questo:

Thing thing("uiae");

crea un oggetto statico che viene distrutto automaticamente all'uscita dall'ambito corrente.


1
Sfortunatamente, questo è davvero il modo più comune di creare nuovi oggetti dinamici invece di usare auto_ptr, unique_ptr o relativi.
Fred Nurk,

3
La domanda del PO era corretta, questa risposta riguarda interamente un altro problema (vedi la risposta di @ JaredPar)
Silmathoron

21

Il compilatore potrebbe anche ottimizzare il secondo modulo nel primo modulo, ma non è necessario.

#include <iostream>

class A
{
    public:
        A() { std::cerr << "Empty constructor" << std::endl; }
        A(const A&) { std::cerr << "Copy constructor" << std::endl; }
        A(const char* str) { std::cerr << "char constructor: " << str << std::endl; }
        ~A() { std::cerr << "destructor" << std::endl; }
};

void direct()
{
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
    A a(__FUNCTION__);
    static_cast<void>(a); // avoid warnings about unused variables
}

void assignment()
{
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
    A a = A(__FUNCTION__);
    static_cast<void>(a); // avoid warnings about unused variables
}

void prove_copy_constructor_is_called()
{
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
    A a(__FUNCTION__);
    A b = a;
    static_cast<void>(b); // avoid warnings about unused variables
}

int main()
{
    direct();
    assignment();
    prove_copy_constructor_is_called();
    return 0;
}

Uscita da gcc 4.4:

TEST: direct
char constructor: direct
destructor

TEST: assignment
char constructor: assignment
destructor

TEST: prove_copy_constructor_is_called
char constructor: prove_copy_constructor_is_called
Copy constructor
destructor
destructor

Qual è lo scopo dei cast statici da annullare?
Stephen Cross,

1
@Stephen Evita avvisi sulle variabili non utilizzate.
Douglas Leeder,

10

Molto semplicemente, entrambe le linee creano l'oggetto sullo stack, anziché sull'heap come fa 'nuovo'. La seconda riga in realtà comporta una seconda chiamata a un costruttore di copie, quindi dovrebbe essere evitata (deve anche essere corretta come indicato nei commenti). Dovresti usare la pila per piccoli oggetti il ​​più possibile poiché è più veloce, tuttavia se i tuoi oggetti sopravvivranno più a lungo della cornice della pila, è chiaramente la scelta sbagliata.


Per chi non ha familiarità con la differenza tra l'istanza di oggetti nello stack e non nell'heap (che utilizza il nuovo e non il nuovo ), ecco un buon thread.
edmqkk,

2

Idealmente, un compilatore dovrebbe ottimizzare il secondo, ma non è necessario. Il primo è il modo migliore. Tuttavia, è piuttosto fondamentale comprendere la distinzione tra stack e heap in C ++, sin da quando è necessario gestire la propria memoria heap.


Il compilatore può garantire che il costruttore di copie non abbia effetti collaterali (come I / O)?
Stephen Cross,

@Stephen - non importa se il costruttore di copie esegue l'I / O - vedi la mia risposta stackoverflow.com/questions/2722879/…
Douglas Leeder

Ok, vedo, al compilatore è permesso di trasformare il secondo modulo nel primo, evitando così la chiamata al costruttore della copia.
Stephen Cross,

2

Ci ho giocato un po 'e la sintassi sembra diventare abbastanza strana quando un costruttore non accetta argomenti. Lasciami fare un esempio:

#include <iostream> 

using namespace std;

class Thing
{
public:
    Thing();
};

Thing::Thing()
{
    cout << "Hi" << endl;
}

int main()
{
    //Thing myThing(); // Does not work
    Thing myThing; // Works

}

quindi basta scrivere Thing myThing senza parentesi in realtà chiama il costruttore, mentre Thing myThing () rende la cosa del compilatore che si desidera creare un puntatore a funzione o qualcosa del genere ?? !!


6
Questa è un'ambiguità sintattica ben nota in C ++. Quando si scrive "int rand ()", il compilatore non può sapere se si intende "creare un int e inizializzarlo come predefinito" o "dichiarare la funzione rand". La regola è che sceglie quest'ultima quando possibile.
jpalecek,

1
E questo, gente, è l' analisi più irritante .
Marc.277 del

2

In aggiunta alla risposta di JaredPar

1 solito ctor, 2a funzione simile a ctor con oggetto temporaneo.

Compila questa fonte da qualche parte qui http://melpon.org/wandbox/ con diversi compilatori

// turn off rvo for clang, gcc with '-fno-elide-constructors'

#include <stdio.h>
class Thing {
public:
    Thing(const char*){puts(__FUNCTION__ );}
    Thing(const Thing&){puts(__FUNCTION__ );}   
    ~Thing(){puts(__FUNCTION__);}
};
int main(int /*argc*/, const char** /*argv*/) {
    Thing myThing = Thing("asdf");
}

E vedrai il risultato.

Da ISO / IEC 14882 2003-10-15

8.5, parte 12

La tua prima, seconda costruzione sono chiamate inizializzazione diretta

12.1, parte 13

Una conversione del tipo di notazione funzionale (5.2.3) può essere utilizzata per creare nuovi oggetti del suo tipo. [Nota: la sintassi sembra una chiamata esplicita del costruttore. ] ... Un oggetto creato in questo modo è senza nome. [Nota: 12.2 descrive la durata degli oggetti temporanei. ] [Nota: le chiamate esplicite del costruttore non generano valori, vedere 3.10. ]


Dove leggere su RVO:

12 Funzioni speciali dei membri / 12.8 Copia di oggetti di classe / Parte 15

Quando vengono soddisfatti determinati criteri, un'implementazione può omettere la costruzione della copia di un oggetto di classe, anche se il costruttore della copia e / o il distruttore per l'oggetto hanno effetti collaterali .

Disattivalo con flag di compilazione dal commento per visualizzare tale comportamento di copia)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.