C++11 rvalue reference calling copy constructor too


He estado probando algunas características de C++11 de some some. Me encontré con referencias de valor r y constructores de movimiento.

Implementé mi primer constructor de movimiento, aquí está:

#include <iostream>
#include <vector>
using namespace std;

class TestClass{

public:
    TestClass(int s):
        size(s), arr(new int[s]){
    }
    ~TestClass(){
        if (arr)
            delete arr;
    }
    // copy constructor
    TestClass(const TestClass& other):
            size(other.size), arr(new int[other.size]){
        std::copy(other.arr, other.arr + other.size, arr);
    }

    // move constructor
    TestClass(TestClass&& other){
        arr=other.arr;
        size=other.size;

        other.arr=nullptr;
        other.size=0;
    }

private:
    int size;
    int * arr;
};

int main(){
    vector<TestClass> vec;

    clock_t start=clock();
    for(int i=0;i<500000;i++){
        vec.push_back(TestClass(1000));
    }
    clock_t stop=clock();
    cout<<stop-start<<endl;

    return 0;
}

El código funciona bien. De todos modos poniendo un std:: cout dentro del constructor de copia me di cuenta de que se llama! Y muchas veces.. (mover constructor 500000 veces, copiar constructor 524287 veces).

Lo que me sorprendió más es que si comento el constructor de copia del código, todo el el programa se vuelve mucho más rápido, y esta vez el constructor move se llama 1024287 veces.

Alguna pista?

Author: R. Martinho Fernandes, 2013-08-06

4 answers

Pon noexcept en tu movimiento constructor:

TestClass(TestClass&& other) noexcept {

Elaboración: Iba a dar este Pierre, pero desafortunadamente la fuente cppreference es solo aproximadamente correcta.

En C++03

vector<T>::push_back(T)

Tiene la "fuerte garantía de excepción". Eso significa que si el push_back lanza una excepción, el vector se deja en el mismo estado que tenía antes de la llamada a push_back.

Esta garantía es problemática si el constructor move lanza una excepción.

Cuando el vector reasigna, sería como a mover los elementos del búfer viejo al nuevo. Sin embargo, si cualquiera de esos movimientos lanza una excepción (además del primero), entonces se deja en un estado donde el búfer antiguo ha sido modificado, y el nuevo búfer aún no contiene todo lo que se supone que debe. El vector no puede restaurar el búfer antiguo a su estado original porque tendría que mover elementos de nuevo para hacerlo, esos movimientos también podrían fallar.

So se estableció una regla para C++11:

  1. Si T tiene un constructor move noexcept, se puede usar para mover los elementos del búfer antiguo al nuevo.

  2. De lo contrario, si T tiene un constructor de copia, se utilizará en su lugar.

  3. De lo contrario (si no hay un constructor de copia accesible), se utilizará el constructor de movimiento después de todo, sin embargo, en este caso, la fuerte garantía de seguridad de excepción ya no es dar.

Aclaración: "copy constructor" en la regla 2 significa un constructor que toma un const T&, no uno de esos weenie llamados T& constructores de copia. :-)

 31
Author: Howard Hinnant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-06 16:45:41

Usa noexcept en tu move constructor:

TestClass(TestClass&& other) noexcept { ... }

noexcept sin una expresión constante como esta es equivalente a noexcept(true).

El compilador puede usar esta información para habilitar ciertas optimizaciones en funciones que no arrojan, así como habilitar el operador noexcept, que puede verificar en tiempo de compilación si se declara que una expresión en particular arroja alguna excepción.

Por ejemplo, contenedores como std:: vector moverán sus elementos si los elementos se mueven constructor es noexcept, y copia de lo contrario.

Fuente : http://en.cppreference.com/w/cpp/language/noexcept_spec

NB : Esta es una característica C++11. Es posible que cierto compilador no lo haya implementado todavía... (ej: Visual Studio 2012)

 14
Author: Pierre Fourgeaud,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-06 16:34:17

Se llama al constructor Copy cuando se usa toda la memoria reservada dentro de std::vector. Es necesario llamar al método std::vector::reserve() antes de agregar los elementos.

vector<TestClass> vec;
vec.reserve(500000);
 0
Author: maestroIgor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-02 16:24:40

Otra pregunta. En el constructor move,

// move constructor
TestClass(TestClass&& other){
    arr=other.arr;
    size=other.size;

    other.arr=nullptr;
    other.size=0;
}

No Debería ser

Arr = std: move(otro.arr);

Size=std:mover(otro.tamaño);

Porque

El hecho de que todos los valores nombrados (como los parámetros de función) siempre se evalúan como lvalues (incluso los declarados como referencias rvalue)

?

 -1
Author: user557583,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-13 05:43:01