tipos sintaxis parametros funciones ejemplos datos con comandos codigos basicos c++ c++11

parametros - sintaxis de c++ pdf



¿Qué sucede técnicamente en este código C++? (4)

A::A

El constructor se ejecuta, cuando se crea el objeto temporal.

primero A :: A (A &&)

El objeto temporal se mueve a la lista de inicialización (que también es rvalue).

segundo A :: A (A &&)

La lista de inicialización se mueve al constructor del vector.

primero A :: A (A &)

El vector se copia porque el constructor de B toma el valor l, y se pasa un valor r.

segundo A :: A (A &)

Nuevamente, el vector se copia al crear la variable miembro de B va .

A :: ~ A
A :: ~ A
A :: ~ A
A :: ~ A
A :: ~ A

Se llama a Destructor para cada valor de r y valor (cada vez que se llama al constructor, copiar o mover constructores, se ejecuta el destructor cuando se destruyen los objetos).

Tengo una clase B que contiene un vector de clase A Quiero inicializar este vector a través del constructor. La clase A genera cierta información de depuración para que pueda ver cuándo se construye, destruye, copia o mueve.

#include <vector> #include <iostream> using namespace std; class A { public: A() { cout << "A::A" << endl; } ~A() { cout << "A::~A" << endl; } A(const A& t) { cout <<"A::A(A&)" << endl; } A(A&& t) { cout << "A::A(A&&)" << endl; } }; class B { public: vector<A> va; B(const vector<A>& va) : va(va) {}; }; int main(void) { B b({ A() }); return 0; }

Ahora, cuando ejecuto este programa (compilado con la opción GCC -fno-elide-constructors para que las llamadas al constructor de movimiento no se optimicen) obtengo el siguiente resultado:

A::A A::A(A&&) A::A(A&&) A::A(A&) A::A(A&) A::~A A::~A A::~A A::~A A::~A

Entonces, en lugar de solo una instancia de A el compilador genera cinco instancias de ello. A se mueve dos veces y se copia dos veces. No esperaba eso. El vector se pasa por referencia al constructor y luego se copia en el campo de clase. Por lo tanto, habría esperado una única operación de copia o simplemente una operación de movimiento (porque esperaba que el vector que pase al constructor sea solo un valor), no dos copias y dos movimientos. ¿Puede alguien explicar qué sucede exactamente en este código? ¿Dónde y por qué crea todas estas copias de A ?


Considera esto:

  1. La A temporal es instanciada: A()
  2. Esa instancia se mueve a la lista de inicializadores: A(A&&)
  3. La lista de inicializadores se mueve al vector ctor, por lo que sus elementos se mueven: A(A&&) . EDITAR : Como TC notó, los elementos initializer_list no se mueven / copian para initializer_list moving / copying. Como muestra su ejemplo de código, parece que se usan dos llamadas de rtorue ctor durante la inicialización de initializer_list.
  4. El elemento de vector se inicializa por valor, en lugar de por movimiento (¿Por qué? No estoy seguro): A(const A&) EDIT: De nuevo, no es el vector sino la lista de inicializadores
  5. Su ctor obtiene ese vector temporal y lo copia (tenga en cuenta su inicializador vectorial), por lo que los elementos se copian: A(const A&)

Intente dividir el código un poco para comprender mejor el comportamiento:

int main(void) { cout<<"Begin"<<endl; vector<A> va({A()}); cout<<"After va;"<<endl; B b(va); cout<<"After b;"<<endl; return 0; }

La salida es similar (tenga en cuenta que se utilizan los -fno-elide-constructors )

Begin A::A <-- temp A() A::A(A&&) <-- moved to initializer_list A::A(A&&) <-- no idea, but as @Manu343726, it''s moved to vector''s ctor A::A(A&) <-- copied to vector''s element A::~A A::~A A::~A After va; A::A(A&) <-- copied to B''s va After b; A::~A A::~A


Podría ser útil pasar por las llamadas del constructor en orden inverso.

B b({ A() });

Para construir una B , el compilador debe llamar al constructor de B que toma un const vector<A>& . Ese constructor, a su vez, debe hacer una copia del vector, incluyendo todos sus elementos. Esa es la segunda copia de la llamada al ctor que ves.

Para construir el vector temporal que se pasará al constructor de B , el compilador debe invocar el constructor initializer_list de std::vector . Ese constructor, a su vez, debe hacer una copia de lo que está contenido en la lista de initializer_list * . Esa es la primera copia del constructor de llamadas que ves.

El estándar especifica cómo se construyen los objetos initializer_list en §8.5.4 [dcl.init.list] / p5:

Un objeto de tipo std::initializer_list<E> se construye a partir de una lista de inicializadores como si la implementación asignara una matriz de N elementos de tipo const E ** , donde N es el número de elementos en la lista de inicializadores. Cada elemento de esa matriz se inicializa con copia con el elemento correspondiente de la lista de inicializadores, y el objeto std::initializer_list<E> se construye para referirse a esa matriz.

La inicialización de copia de un objeto de algo del mismo tipo utiliza la resolución de sobrecarga para seleccionar el constructor a usar (§8.5 [dcl.init] / p17), por lo que con un valor de r del mismo tipo invocará el constructor de movimiento si es que está disponible. Por lo tanto, para construir el initializer_list<A> partir de la lista de inicializadores reforzados, el compilador primero construirá una matriz de una const A moviéndose desde el A temporal construido por A() , provocando una llamada del constructor de movimiento, y luego construirá el objeto initializer_list para referirse a esa matriz.

Sin embargo, no puedo entender de dónde viene el otro movimiento en g ++. initializer_list s normalmente no son más que un par de punteros, y los mandatos estándar que copian uno no copian los elementos subyacentes. g ++ parece llamar al constructor de movimientos dos veces al crear una lista de initializer_list partir de un temporal. Incluso llama al constructor de movimientos cuando construye una initializer_list partir de un valor l.

Mi mejor conjetura es que está implementando literalmente el ejemplo no normativo del estándar. El estándar proporciona el siguiente ejemplo:

struct X { X(std::initializer_list<double> v); }; X x{ 1,2,3 };

La inicialización se implementará de manera aproximadamente equivalente a esto: **

const double __a[3] = {double{1}, double{2}, double{3}}; X x(std::initializer_list<double>(__a, __a+3));

asumiendo que la implementación puede construir un objeto initializer_list con un par de punteros.

Entonces, si toma este ejemplo literalmente, la matriz que subyace en el initializer_list en nuestro caso se construirá como si:

const A __a[1] = { A{A()} };

lo que incurre en dos llamadas de constructor de movimiento porque construye una A temporal, la copia inicializa una segunda A temporal desde la primera, y luego la copia inicializa el miembro de la matriz desde la segunda temporal. El texto normativo de la norma, sin embargo, deja en claro que solo debe haber una copia de inicialización, no dos, por lo que esto parece un error.

Finalmente, el primer A::A viene directamente de A() .

No hay mucho que discutir sobre las llamadas destructoras. Todos los temporales (independientemente del número) creados durante la construcción de b se destruirán al final de la declaración en el orden inverso de construcción, y la A almacenada en b se destruirá cuando b salga del alcance.

* Los constructores initializer_list de los contenedores de biblioteca estándar se definen como equivalentes a invocar al constructor tomando dos iteradores con list.begin() y list.end() . Esas funciones miembro devuelven una const T* , por lo que no se puede mover desde. En C ++ 14, la matriz de respaldo se hace const , por lo que es aún más claro que no se puede mover de ella o cambiarla.

** Esta respuesta originalmente citó N3337 (el estándar C ++ 11 más algunos cambios editoriales menores), que tiene la matriz con elementos de tipo E lugar de const E y la matriz en el ejemplo es de tipo double . En C ++ 14, la matriz subyacente se hizo const como resultado de CWG 1418 .