2010-04-27 6 views
100

menudo he visto que la gente crea objetos en C++ usandoCalling constructores en C++ sin nueva

Thing myThing("asdf"); 

lugar de esto:

Thing myThing = Thing("asdf"); 

Esto parece funcionar (con gcc), al menos siempre que no haya plantillas involucradas. Mi pregunta ahora, ¿es correcta la primera línea y, de ser así, debería usarla?

+15

formar o bien carece de nuevo. –

+10

El segundo formulario utilizará el constructor de copia, por lo que no, no son equivalentes. –

+0

Jugué un poco con eso, la primera forma parece fallar a veces cuando las plantillas se usan con constructores sin parámetros ... – Nils

Respuesta

110

Ambas líneas son de hecho correctas pero sutilmente son cosas diferentes.

La primera línea crea un nuevo objeto en la pila llamando a un constructor del formato Thing(const char*).

El segundo es un poco más complejo. En esencia, hace lo siguiente

  1. Crear un objeto de tipo Thing utilizando el constructor Thing(const char*)
  2. Crear un objeto de tipo Thing utilizando el constructor Thing(const Thing&)
  3. llamada ~Thing() en el objeto creado en el paso # 1
+7

Supongo que estos tipos de acciones están optimizados y, por lo tanto, no difieren significativamente en aspectos de rendimiento. –

+13

No creo que sus pasos sean correctos. 'Thing myThing = Thing (...) 'no utiliza el operador de asignación, sigue siendo de copia como diciendo' Thing myThing (Thing (...)) ', y no implica una' Thing' construida por defecto (edición: la publicación se corrigió posteriormente) – AshleysBrain

+1

Entonces puede decir que la segunda línea es incorrecta porque desperdicia recursos sin ninguna razón aparente. Por supuesto, es posible que la creación de la primera instancia sea intencional para algunos efectos secundarios, pero eso es aún peor (estilísticamente). –

7

En pocas palabras, ambas líneas crean el objeto en la pila, en lugar de en el montón como 'nuevo'. La segunda línea en realidad implica una segunda llamada a un constructor de copia, por lo que debe evitarse (también debe corregirse como se indica en los comentarios). Debe usar la pila para objetos pequeños tanto como sea posible, ya que es más rápido, sin embargo, si sus objetos van a sobrevivir durante más tiempo que el marco de pila, entonces es claramente la opción incorrecta.

27

asumo con la segunda línea en realidad se refiere a:

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

que sería la forma estándar de la creación de nuevos dinámicas objetos (necesaria para la ligadura dinámica y el polimorfismo) y almacenar su dirección a un puntero. Su código hace lo que JaredPar describió, a saber, crear dos objetos (uno pasó un const char*, el otro pasó un const Thing&), y luego llamar al destructor (~Thing()) en el primer objeto (el const char* uno).

Por el contrario, esto:

Thing thing("uiae"); 

crea un objeto estático que se destruye automáticamente al salir el ámbito actual.

+1

Desafortunadamente, esa es de hecho la forma más común de crear nuevos objetos dinámicos en lugar de usar auto_ptr, unique_ptr o related. –

+1

La pregunta del OP era correcta, esta respuesta se refiere a otro problema por completo (ver la respuesta de @JaredPar) – Silmathoron

2

Idealmente, un compilador optimizaría el segundo, pero no es necesario. El primero es la mejor manera. Sin embargo, es muy importante entender la distinción entre stack y heap en C++, ya que debes administrar tu propia memoria Heap.

+0

¿Puede el compilador garantizar que el constructor de copia no tenga efectos secundarios (como E/S)? –

+0

@Stephen - no importa si el constructor de copia hace E/S - vea mi respuesta http://stackoverflow.com/questions/2722879/calling-constructors-in-c-without-new/2723266#2723266 –

+0

Ok , Veo, el compilador puede convertir el segundo formulario en el primero y, por lo tanto, evita la llamada al constructor de copia. –

16

El compilador bien puede optimizar la segunda forma en la primera forma, pero no tiene por qué.

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

Salida 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 
+0

¿Cuál es el propósito de los moldes estáticos de anular? –

+1

@Stephen Evite advertencias sobre variables no utilizadas. –

0

Jugué un poco con él y la sintaxis parece ser bastante extraña cuando un constructor no toma argumentos. Déjeme darle un ejemplo:

#include <iostream> 

using namespace std; 

class Thing 
{ 
public: 
    Thing(); 
}; 

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

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

} 

por lo que acaba de escribir cosa MYTHing w/o soportes en realidad llama al constructor, mientras cosa MYTHing() hace que el compilador lo que desea crear un puntero de función o algo ?? !!

+4

Esta es una ambigüedad sintáctica bien conocida en C++. Cuando escribe "int rand()", el compilador no puede saber si quiere decir "crear un int y default-initialize it" o "declare function rand". La regla es que elige lo último siempre que sea posible. – jpalecek

1

En anexados para JaredPar respuesta

ctor

1-habitual, segunda función similar a-ctor con el objeto temporal.

compilar este código en algún lugar aquí http://melpon.org/wandbox/ con diferentes compiladores

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

y verá el resultado.

A partir de ISO/IEC 14882 2003-10-15

8,5, parte 12

su primera, segunda construcción son llamados directo inicialización

12,1, parte 13

Se puede utilizar una conversión de tipo de notación funcional (5.2.3) para crear nuevos objetos de su tipo. [Nota: la sintaxis se parece a una llamada explícita del constructor. ] ... Un objeto creado de esta manera no tiene nombre. [Nota: 12.2 describe la vida útil de los objetos temporales. ] [Nota: llamadas explícitas de constructor no arrojan valores l, véase 3.10. ]


donde leer sobre RVO:

12 funciones miembro especiales/12,8 copia objetos de clase/Parte 15

Cuando se cumplen ciertos criterios, se permite una implementación omitir copia de construcción de un objeto de clase, incluso si el constructor de copia y/o destructor para el objeto tiene efectos secundarios.

apagarlo con la bandera del compilador de comentario para ver dicha copia en el comportamiento)

Cuestiones relacionadas