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?


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

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.


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

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.


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.


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 ?? !!


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)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.