Llamar a constructores en c ++ sin novedades

142

A menudo he visto que las personas crean objetos en C ++ usando

Thing myThing("asdf");

En lugar de esto:

Thing myThing = Thing("asdf");

Esto parece funcionar (usando gcc), al menos mientras no haya plantillas involucradas. Mi pregunta ahora, ¿es correcta la primera línea y si es así debo usarla?

Nils
fuente
25
Cualquiera de las formas es sin nuevo.
Daniel Daranas
13
La segunda forma usará el constructor de copia, así que no, no son equivalentes.
Edward Strange
He jugado un poco con ella, la primera forma parece fallar a veces, cuando se utilizan las plantillas con los constructores sin parámetros ..
Nils
1
Ouh y yo obtuvimos la insignia de "Buena pregunta" por eso, ¡qué pena!
Nils

Respuestas:

153

Ambas líneas son correctas, pero hacen cosas sutilmente 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. Esencialmente hace lo siguiente

  1. Crear un objeto de tipo Thingusando el constructorThing(const char*)
  2. Crear un objeto de tipo Thingusando el constructorThing(const Thing&)
  3. Llame ~Thing()al objeto creado en el paso 1
JaredPar
fuente
77
Supongo que este tipo de acciones están optimizadas y, por lo tanto, no difieren significativamente en los aspectos de rendimiento.
M. Williams
14
No creo que tus pasos sean correctos. Thing myThing = Thing(...)no utiliza el operador de asignación, sigue siendo una copia construida como se dice Thing myThing(Thing(...)), y no implica una construcción predeterminada Thing(editar: la publicación se corrigió posteriormente)
AshleysBrain
1
Entonces puede decir que la segunda línea es incorrecta, porque desperdicia recursos sin 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).
MK.
3
No, @Jared, no está garantizado. Pero incluso si el compilador elige realizar esa optimización, el constructor de copias aún debe ser accesible (es decir, no protegido o privado), incluso si no está implementado o llamado.
Rob Kennedy el
3
Parece que la copia se puede eliminar incluso si el constructor de la copia tiene efectos secundarios. Vea mi respuesta: stackoverflow.com/questions/2722879/…
Douglas Leeder
31

Supongo que con la segunda línea realmente quieres decir:

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

cuál sería la forma estándar de crear nuevos objetos dinámicos (necesarios para el enlace dinámico y el polimorfismo) y almacenar su dirección en un puntero. Su código hace lo que JaredPar describió, es decir, crear dos objetos (uno pasó a const char*, el otro pasó a 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 del alcance actual.

knittl
fuente
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 relacionados.
Fred Nurk
3
La pregunta del OP era correcta, esta respuesta se refiere a otro problema por completo (ver la respuesta de @ JaredPar)
Silmathoron
21

El compilador puede optimizar la segunda forma en la primera forma, pero no tiene que hacerlo.

#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
Douglas Leeder
fuente
¿Cuál es el propósito de los moldes estáticos para anular?
Stephen Cross
1
@Stephen Evite las advertencias sobre variables no utilizadas.
Douglas Leeder
10

En pocas palabras, ambas líneas crean el objeto en la pila, en lugar de en el montón como lo hace 'nuevo'. La segunda línea en realidad implica una segunda llamada a un constructor de copias, 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 por más tiempo que el marco de la pila, entonces es claramente la elección incorrecta.

Stephen Cross
fuente
Para aquellos que no están familiarizados con la diferencia entre crear instancias de objetos en la pila en lugar de en el montón (es decir, usar nuevo y no usar nuevo ), aquí hay un buen hilo.
edmqkk
2

Idealmente, un compilador optimizaría el segundo, pero no es obligatorio. La primera es la mejor manera. Sin embargo, es bastante crítico comprender la distinción entre pila y montón en C ++, ya que debe administrar su propia memoria de montón.

Perrito
fuente
¿Puede el compilador garantizar que el constructor de copia no tenga efectos secundarios (como E / S)?
Stephen Cross
@Stephen - no importa si el constructor de copias hace E / S - mira mi respuesta stackoverflow.com/questions/2722879/…
Douglas Leeder
Ok, ya veo, el compilador puede convertir el segundo formulario en el primero y, por lo tanto, evita la llamada al constructor de la copia.
Stephen Cross
2

Jugué un poco con él y la sintaxis parece ser bastante extraña cuando un constructor no toma argumentos. Déjame dar 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

}

¡así que escribir Thing myThing sin corchetes en realidad llama al constructor, mientras que Thing myThing () hace que el compilador desee crear un puntero de función o algo así!

Nils
fuente
66
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 e inicializarlo por defecto" o "declarar la función rand". La regla es que elige este último siempre que sea posible.
jpalecek
1
Y esto, amigos, es el análisis más irritante .
Marc.2377
2

En anexar a JaredPar respuesta

1-habitual ctor, 2-función-como-ctor con objeto temporal.

Compile esta fuente 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ás el resultado.

De ISO / IEC 14882 2003-10-15

8.5, parte 12

Su primera y segunda construcción se llaman inicialización directa

12.1, parte 13

Se puede usar una conversión de tipo de notación funcional (5.2.3) para crear nuevos objetos de su tipo. [Nota: La sintaxis parece 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: las llamadas explícitas de constructor no producen valores de l, ver 3.10. ]


Dónde leer sobre RVO:

12 Funciones especiales para miembros / 12.8 Copia de objetos de clase / Parte 15

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

Desactívelo con el indicador del compilador del comentario para ver dicho comportamiento de copia)

bruziuz
fuente