¿Los paréntesis después del nombre del tipo marcan una diferencia con new?

11 minutos de lectura

¿Los parentesis despues del nombre del tipo marcan una diferencia
david leer

Si ‘Prueba’ es una clase ordinaria, ¿hay alguna diferencia entre:

Test* test = new Test;

y

Test* test = new Test();

  • Esto está relacionado con (pero no es idéntico a) stackoverflow.com/questions/1613341/…

    –Steve Jessop

    7 de diciembre de 2010 a las 17:21

  • Simplemente use new Test () para asegurarse de que esté inicializado en cero

    – Cantado

    6 de julio de 2016 a las 5:51

1647628090 70 ¿Los parentesis despues del nombre del tipo marcan una diferencia
miguel rebabas

Pongámonos pedantes, porque hay diferencias que realmente pueden afectar el comportamiento de su código. Gran parte de lo que sigue está tomado de comentarios hechos a un Artículo “Vieja novedad”.

A veces, la memoria devuelta por el operador new se inicializará y, a veces, no, dependiendo de si el tipo que está renovando es un POD (datos antiguos simples) o si es una clase que contiene miembros de POD y está usando un constructor predeterminado generado por el compilador.

  • En C++1998 hay 2 tipos de inicialización: cero y por defecto
  • En C ++ 2003, se agregó un tercer tipo de inicialización, inicialización de valor.

Asumir:

struct A { int m; }; // POD
struct B { ~B(); int m; }; // non-POD, compiler generated default ctor
struct C { C() : m() {}; ~C(); int m; }; // non-POD, default-initialising m

En un compilador de C++98, debería ocurrir lo siguiente:

  • new A – valor indeterminado
  • new A() – cero inicializar

  • new B – construcción predeterminada (B::m no está inicializado)

  • new B() – construcción predeterminada (B::m no está inicializado)

  • new C – construcción predeterminada (C::m se inicializa en cero)

  • new C() – construcción predeterminada (C::m se inicializa en cero)

En un compilador compatible con C++03, las cosas deberían funcionar así:

  • new A – valor indeterminado
  • new A() – value-initialize A, que es inicialización cero ya que es un POD.

  • new B – inicializa por defecto (deja B::m sin inicializar)

  • new B() – value-initializes B que inicializa en cero todos los campos ya que su ctor predeterminado es generado por el compilador en lugar de definido por el usuario.

  • new C – predeterminado: inicializa C, que llama al ctor predeterminado.

  • new C() – value-inicializa C, que llama al ctor predeterminado.

Así que en todas las versiones de C++ hay una diferencia entre new A y new A() porque A es un POD.

Y hay una diferencia de comportamiento entre C++98 y C++03 para el caso new B().

Este es uno de los rincones polvorientos de C++ que puede volverte loco. Al construir un objeto, a veces quieres/necesitas los paréntesis, a veces no puedes tenerlos en absoluto y, a veces, no importa.

  • @j_random_hacker, new A() inicializará por defecto el objeto en C++98, como lo hace con new B(), new B, new C() y new Cpero no con new A. Es decir, la inicialización predeterminada siempre se realiza en C++98 cuando: 1) la clase no es un POD y falta el inicializador, o 2) el inicializador es (). default-initialization cero-inicializa el objeto si es un POD, pero llama al constructor predeterminado para no POD.

    – Johannes Schaub – litb

    2 de enero de 2011 a las 13:32


  • ¿Alguien puede agregar cuál es el caso en C++ 11 ahora?

    – leyendas2k

    21 de agosto de 2012 a las 4:39


  • @Jon: con C++ 11 también puedes hacer esto en la pila; B obj{}; hará que el valor del objeto se inicialice (a 0) en lugar de B obj; que se inicializará por defecto (basura).

    – leyendas2k

    7 mayo 2013 a las 14:03

  • Dices que “a veces absolutamente no puedes tenerlos [parentheses]”. ¿Qué situación es donde no puedes agregarlos?

    – kec

    25 de abril de 2015 a las 17:07

  • Así que el tl; dr es que new A da a los miembros un valor indeterminado y new A() inicializa los valores de los miembros a 0… a menos que A tiene un destructor definido, en cuyo caso ambas expresiones dan a los miembros valores indeterminados… a menos que A también tiene un constructor definido, en cuyo caso ambas expresiones inicializan en cero a los miembros… a menos que sea un compilador C++03, en cuyo caso new A() en su lugar, “valor inicializará” a los miembros, lo cual es diferente de alguna manera (?). Tan sencillo.

    – Blue Raja – Danny Pflughoeft

    06/03/2017 a las 20:02

1647628090 843 ¿Los parentesis despues del nombre del tipo marcan una diferencia
kfson

new Thing(); es explícito que desea un constructor llamado mientras que new Thing; se toma para implicar que no le importa si no se llama al constructor.

Si se usa en una estructura/clase con un constructor definido por el usuario, no hay diferencia. Si se llama a una estructura/clase trivial (p. ej. struct Thing { int i; };) luego new Thing; es como malloc(sizeof(Thing)); mientras que new Thing(); es como calloc(sizeof(Thing)); – se pone cero inicializado.

El gotcha se encuentra en el medio:

struct Thingy {
  ~Thingy(); // No-longer a trivial class
  virtual WaxOn();
  int i;
};

el comportamiento de new Thingy; contra new Thingy(); en este caso cambió entre C++98 y C++2003. Vea la explicación de Michael Burr sobre cómo y por qué.

No ellos son los mismos. Pero hay una diferencia entre:

Test t;      // create a Test called t

y

Test t();   // declare a function called t which returns a Test

Esto se debe a la regla básica de C++ (y C): si algo puede ser una declaración, entonces es una declaración.

Editar: Con respecto a los problemas de inicialización con respecto a los datos POD y no POD, si bien estoy de acuerdo con todo lo que se ha dicho, solo me gustaría señalar que estos problemas solo se aplican si la cosa que se está renovando o construyendo no tiene un usuario- constructor definido. Si existe tal constructor, se utilizará. Para el 99,99% de las clases diseñadas con sensatez, habrá un constructor de este tipo, por lo que los problemas pueden ignorarse.

  • Tenga en cuenta que este es un punto particularmente importante porque la línea “Test t(5);” es equivalente a “Prueba t = Prueba (5);” — pero “Prueba t();” es muy diferente de “Test t = Test();”. +1

    – ojrac

    6 de marzo de 2009 a las 20:03

  • -1, no estoy de acuerdo con su declaración de que los problemas pueden ignorarse. No es necesario que conozca las reglas con precisión, pero debe conocerlas en caso de que tenga que crear una nueva clase sin un constructor predeterminado definido por el usuario (entonces debe escribir el constructor o buscar las reglas).

    – Avakar

    6 de marzo de 2010 a las 7:02

  • -1 para una respuesta incorrecta conocida. Su edición ignora la presencia de código escrito por antiguos programadores de C que no entendían ni usaban constructores.

    – Tomás

    16 de abril de 2010 a las 11:00

  • ¿Qué pasa con las clases como struct point { float v[3]; };? Para cosas como esa, un constructor sería una mala idea, ya que evitaría todas las buenas propiedades que vienen con ser POD y agregado. Entonces, “los problemas pueden ignorarse” está mal, en mi opinión.

    – yo22

    2 de enero de 2011 a las 3:56

  • Pero no son los mismos. Esta respuesta es simplemente incorrecta. Debería corregirse o eliminarse, porque parece haber causado cierta confusión, a juzgar por la gran cantidad de votos a favor.

    – juanchopanza

    11 de agosto de 2014 a las 5:59

1647628091 391 ¿Los parentesis despues del nombre del tipo marcan una diferencia
bayda

En general, tenemos inicialización predeterminada en el primer caso e inicialización de valor en el segundo caso.

Por ejemplo: en caso de int (tipo POD):

  • int* test = new int – tenemos cualquier inicialización y el valor de *test puede ser cualquiera.

  • int* test = new int() – *test tendrá valor 0.

el siguiente comportamiento dependía de su tipo de prueba. Tenemos diferentes casos: la prueba tiene un constructor predeterminado, la prueba ha generado un constructor predeterminado, la prueba contiene un miembro POD, no miembro POD…

1647628091 411 ¿Los parentesis despues del nombre del tipo marcan una diferencia
Evan Shaw

Asumiendo que Prueba es una clase con un constructor definido, no hay diferencia. La última forma deja un poco más claro que el constructor de Test se está ejecutando, pero eso es todo.

1647628091 588 ¿Los parentesis despues del nombre del tipo marcan una diferencia
Eso es solo cursi

Las reglas para new son análogas a lo que sucede cuando inicializa un objeto con duración de almacenamiento automático (aunque, debido al molesto análisis, la sintaxis puede ser ligeramente diferente).

Si yo digo:

int my_int; // default-initialize → indeterminate (non-class type)

Luego my_int tiene un valor indeterminado, ya que es un tipo que no es de clase. Alternativamente, puedo inicializar el valor my_int (que, para los tipos que no son de clase, se inicializa en cero) así:

int my_int{}; // value-initialize → zero-initialize (non-class type)

(Por supuesto, no puedo usar () porque eso sería una declaración de función, pero int() funciona igual que int{} para construir un temporal.)

Considerando que, para los tipos de clase:

Thing my_thing; // default-initialize → default ctor (class type)
Thing my_thing{}; // value-initialize → default-initialize → default ctor (class type)

Se llama al constructor predeterminado para crear un Thingsin excepciones.

Entonces, las reglas son más o menos:

  • ¿Es un tipo de clase?
    • : Se llama al constructor predeterminado, independientemente de si está inicializado por valor (con {}) o inicializado por defecto (sin {}). (Existe un comportamiento de puesta a cero previo adicional con la inicialización de valor, pero el constructor predeterminado siempre tiene la última palabra).
    • NO: Eran {} ¿usado?
      • : El objeto se inicializa por valor, lo que, para los tipos que no son de clase, más o menos solo se inicializa en cero.
      • NO: el objeto se inicializa por defecto, lo que, para los tipos que no son de clase, lo deja con un valor indeterminado (no se inicializa efectivamente).

Estas reglas se traducen precisamente en new sintaxis, con la regla añadida de que () puede ser sustituido por {} porque new nunca se analiza como una declaración de función. Entonces:

int* my_new_int = new int; // default-initialize → indeterminate (non-class type)
Thing* my_new_thing = new Thing; // default-initialize → default ctor (class type)
int* my_new_zeroed_int = new int(); // value-initialize → zero-initialize (non-class type)
     my_new_zeroed_int = new int{}; // ditto
       my_new_thing = new Thing(); // value-initialize → default-initialize → default ctor (class type)

(Esta respuesta incorpora cambios conceptuales en C++ 11 que la respuesta principal actualmente no tiene; en particular, una nueva instancia escalar o POD que terminaría con un valor indeterminado ahora está técnicamente inicializada por defecto (que, para los tipos POD, técnicamente llama a un constructor predeterminado trivial). Si bien esto no causa muchos cambios prácticos en el comportamiento, simplifica un poco las reglas).

1647628092 595 ¿Los parentesis despues del nombre del tipo marcan una diferencia
uqb

Escribí algunos códigos de muestra a continuación, como complemento a la respuesta de Michael Burr:

#include <iostream>

struct A1 {
    int i;
    int j;
};

struct B {
    int k;
    B() : k(4) {}
    B(int k_) : k(k_) {}
};

struct A2 {
    int i;
    int j;
    B b;
};

struct A3 {
    int i;
    int j;
    B b;
    A3() : i(1), j(2), b(5) {}
    A3(int i_, int j_, B b_): i(i_), j(j_), b(b_) {}
};

int main() {
    {
        std::cout << "Case#1: POD without ()\n";
        A1 a1 = {1, 2};
        std::cout << a1.i << " " << a1.j << std::endl;
        A1* a = new (&a1) A1;
        std::cout << a->i << " " << a->j  << std::endl;
    }
    {
        std::cout << "Case#2: POD with ()\n";
        A1 a1 = {1, 2};
        std::cout << a1.i << " " << a1.j << std::endl;
        A1* a = new (&a1) A1();
        std::cout << a->i << " " << a->j  << std::endl;
    }
    {
        std::cout << "Case#3: non-POD without ()\n";
        A2 a1 = {1, 2, {3}};
        std::cout << a1.i << " " << a1.j << " " << a1.b.k << std::endl;
        A2* a = new (&a1) A2;
        std::cout << a->i << " " << a->j << " " << a->b.k << std::endl;
    }
    {
        std::cout << "Case#4: non-POD with ()\n";
        A2 a1 = {1, 2, {3}};
        std::cout << a1.i << " " << a1.j << " " << a1.b.k  << std::endl;
        A2* a = new (&a1) A2();
        std::cout << a->i << " " << a->j << " " << a1.b.k << std::endl;
    }
    {
        std::cout << "Case#5: user-defined-ctor class without ()\n";
        A3 a1 = {11, 22, {33}};
        std::cout << a1.i << " " << a1.j << " " << a1.b.k << std::endl;
        A3* a = new (&a1) A3;
        std::cout << a->i << " " << a->j << " " << a->b.k << std::endl;
    }
    {
        std::cout << "Case#6: user-defined-ctor class with ()\n";
        A3 a1 = {11, 22, {33}};
        std::cout << a1.i << " " << a1.j << " " << a1.b.k  << std::endl;
        A3* a = new (&a1) A3();
        std::cout << a->i << " " << a->j << " " << a1.b.k << std::endl;
    }
    return 0;
}

/*
output with GCC11.1(C++20)
Case#1: POD without ()
1 2
1 2
Case#2: POD with ()
1 2
0 0
Case#3: non-POD without ()
1 2 3
1 2 4
Case#4: non-POD with ()
1 2 3
0 0 4
Case#5: user-defined-ctor class without ()
11 22 33
1 2 5
Case#6: user-defined-ctor class with ()
11 22 33
1 2 5
*/

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad