Appel de constructeurs en C ++ sans nouveau

142

J'ai souvent vu que les gens créent des objets en C ++ en utilisant

Thing myThing("asdf");

Au lieu de cela:

Thing myThing = Thing("asdf");

Cela semble fonctionner (en utilisant gcc), du moins tant qu'il n'y a pas de modèles impliqués. Ma question maintenant, est-ce que la première ligne est correcte et si oui, dois-je l'utiliser?

Nils
la source
25
L'une ou l'autre forme est sans nouveau.
Daniel Daranas
13
Le deuxième formulaire utilisera le constructeur de copie donc non, ils ne sont pas équivalents.
Edward Strange
J'ai joué un peu avec, le premier moyen semble parfois échouer lorsque des modèles sont utilisés avec des constructeurs sans paramètre ..
Nils
1
Ouh et moi avons eu le badge "Nice Question" pour ça, quel dommage!
Nils

Réponses:

153

Les deux lignes sont en fait correctes mais font des choses subtilement différentes.

La première ligne crée un nouvel objet sur la pile en appelant un constructeur du format Thing(const char*).

Le second est un peu plus complexe. Il fait essentiellement ce qui suit

  1. Créer un objet de type Thingà l'aide du constructeurThing(const char*)
  2. Créer un objet de type Thingà l'aide du constructeurThing(const Thing&)
  3. Appelez ~Thing()l'objet créé à l'étape 1
JaredPar
la source
7
Je suppose que ces types d'actions sont optimisés et, par conséquent, ne diffèrent pas de manière significative en termes de performances.
M. Williams
14
Je ne pense pas que vos étapes soient tout à fait correctes. Thing myThing = Thing(...)n'utilise pas l'opérateur d'affectation, il est toujours construit par copie comme le dire Thing myThing(Thing(...)), et n'implique pas de construction par défaut Thing(modifier: le message a été corrigé par la suite)
AshleysBrain
1
Vous pouvez donc dire que la deuxième ligne est incorrecte, car elle gaspille des ressources sans raison apparente. Bien sûr, il est possible que la création de la première instance soit intentionnelle pour certains effets secondaires, mais c'est encore pire (stylistiquement).
MK.
3
Non, @Jared, ce n'est pas garanti. Mais même si le compilateur choisit d'effectuer cette optimisation, le constructeur de copie doit toujours être accessible (c'est-à-dire non protégé ou privé), même s'il n'est pas implémenté ou appelé.
Rob Kennedy
3
Il semble que la copie puisse être élidée même si le constructeur de la copie a des effets secondaires - voir ma réponse: stackoverflow.com/questions/2722879/…
Douglas Leeder
31

Je suppose qu'avec la deuxième ligne, vous voulez dire:

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

qui serait la manière standard de créer de nouveaux objets dynamiques (nécessaires pour la liaison dynamique et le polymorphisme) et de stocker leur adresse dans un pointeur. Votre code fait ce que JaredPar a décrit, à savoir créer deux objets (l'un a passé a const char*, l'autre a passé a const Thing&), puis appeler le destructeur ( ~Thing()) sur le premier objet ( const char*celui).

En revanche, ceci:

Thing thing("uiae");

crée un objet statique qui est détruit automatiquement à la sortie de la portée actuelle.

tricot
la source
1
Malheureusement, c'est en effet la manière la plus courante de créer de nouveaux objets dynamiques au lieu d'utiliser auto_ptr, unique_ptr ou related.
Fred Nurk
3
La question du PO était correcte, cette réponse concerne entièrement une autre question (voir la réponse de @ JaredPar)
Silmathoron
21

Le compilateur peut très bien optimiser la deuxième forme dans la première forme, mais ce n'est pas obligatoire.

#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;
}

Sortie de 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
Douglas Leeder
la source
Quel est le but des moulages statiques d'annuler?
Stephen Cross
1
@Stephen Évitez les avertissements concernant les variables inutilisées.
Douglas Leeder
10

Tout simplement, les deux lignes créent l'objet sur la pile, plutôt que sur le tas comme le fait «new». La deuxième ligne implique en fait un deuxième appel à un constructeur de copie, donc cela doit être évité (il doit également être corrigé comme indiqué dans les commentaires). Vous devriez utiliser la pile pour les petits objets autant que possible car elle est plus rapide, mais si vos objets vont survivre plus longtemps que le cadre de la pile, alors c'est clairement le mauvais choix.

Stephen Cross
la source
Pour ceux qui ne connaissent pas la différence entre instancier des objets sur la pile et sur le tas (c'est-à-dire en utilisant new et non en utilisant new ), voici un bon thread.
edmqkk
2

Idéalement, un compilateur optimiserait le second, mais ce n'est pas obligatoire. Le premier est le meilleur moyen. Cependant, il est assez critique de comprendre la distinction entre pile et tas en C ++, sine vous devez gérer votre propre mémoire de tas.

Chiot
la source
Le compilateur peut-il garantir que le constructeur de copie n'a pas d'effets secondaires (tels que les E / S)?
Stephen Cross
@Stephen - peu importe si le constructeur de copie effectue des E / S - voir ma réponse stackoverflow.com/questions/2722879/…
Douglas Leeder
Ok, je vois, le compilateur est autorisé à transformer le deuxième formulaire en premier et évite ainsi l'appel au constructeur de copie.
Stephen Cross
2

J'ai joué un peu avec et la syntaxe semble devenir assez étrange lorsqu'un constructeur ne prend aucun argument. Laissez-moi vous donner un exemple:

#include <iostream> 

using namespace std;

class Thing
{
public:
    Thing();
};

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

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

}

donc juste écrire Thing myThing sans crochets appelle le constructeur, tandis que Thing myThing () fait du compilateur ce que vous voulez créer un pointeur de fonction ou quelque chose ?? !!

Nils
la source
6
Il s'agit d'une ambiguïté syntaxique bien connue en C ++. Lorsque vous écrivez "int rand ()", le compilateur ne peut pas savoir si vous voulez dire "créer un int et l'initialiser par défaut" ou "déclarer la fonction rand". La règle est qu'il choisit ce dernier chaque fois que possible.
jpalecek
1
Et ceci, mes amis, est l' analyse la plus vexante .
Marc 2377
2

En annexe à JaredPar réponse

1-ctor habituel, 2nd-function-like-ctor avec un objet temporaire.

Compilez cette source quelque part ici http://melpon.org/wandbox/ avec différents compilateurs

// 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");
}

Et vous verrez le résultat.

D'après ISO / CEI 14882 2003-10-15

8.5, partie 12

Votre 1ère, 2ème construction s'appelle l'initialisation directe

12.1, partie 13

Une conversion de type de notation fonctionnelle (5.2.3) peut être utilisée pour créer de nouveaux objets de son type. [Note: La syntaxe ressemble à un appel explicite du constructeur. ] ... Un objet ainsi créé est sans nom. [Remarque: 12.2 décrit la durée de vie des objets temporaires. ] [Remarque: les appels de constructeur explicites ne donnent pas de valeurs l, voir 3.10. ]


Où lire sur RVO:

12 Fonctions membres spéciales / 12.8 Copie d'objets de classe / Partie 15

Lorsque certains critères sont remplis, une implémentation est autorisée à omettre la construction de copie d'un objet de classe, même si le constructeur de copie et / ou le destructeur de l'objet ont des effets secondaires .

Désactivez-le avec l'indicateur du compilateur du commentaire pour afficher un tel comportement de copie)

Bruziuz
la source