c++ - ¿Cómo hacer valer la semántica de movimientos cuando crece un vector?
c++11 resize (3)
Tengo un std::vector
de objetos de cierta clase A
La clase no es trivial y tiene constructores de copia y constructores de movimiento definidos.
std::vector<A> myvec;
Si myvec.push_back(a)
el vector con objetos A
(usando, por ejemplo, myvec.push_back(a)
), el vector crecerá en tamaño, utilizando el constructor de copias A( const A&)
para crear nuevas copias de los elementos en el vector.
¿Puedo de alguna manera hacer cumplir que el constructor de movimiento de la clase A
está siendo utilizado?
Curiosamente, el vector de gcc 4.7.2 solo usa el constructor de movimiento si tanto el constructor de movimiento como el destructor son noexcept
. Un simple ejemplo:
struct foo {
foo() {}
foo( const foo & ) noexcept { std::cout << "copy/n"; }
foo( foo && ) noexcept { std::cout << "move/n"; }
~foo() noexcept {}
};
int main() {
std::vector< foo > v;
for ( int i = 0; i < 3; ++i ) v.emplace_back();
}
Esto produce lo esperado:
move
move
move
Sin embargo, cuando noexcept
no noexcept
de ~foo()
, el resultado es diferente:
copy
copy
copy
Supongo que esto también responde a esta pregunta .
Necesitas informar a C ++ (específicamente std::vector
) que tu constructor de movimiento y destructor no lanzan, usando noexcept
. Luego se llamará al constructor de movimientos cuando el vector crezca.
Esta es la forma de declarar e implementar un constuctor de movimiento que es respetado por std::vector
:
A(A && rhs) noexcept {
std::cout << "i am the move constr" <<std::endl;
... some code doing the move ...
m_value=std::move(rhs.m_value) ; // etc...
}
Si el constructor no es noexcept
, std::vector
no puede usarlo, ya que no puede garantizar las garantías de excepción exigidas por el estándar.
Para obtener más información sobre lo que se dice en el estándar, lea la semántica de C ++ Mover y las excepciones
Crédito a Bo que insinuó que podría tener que ver con excepciones. También considere los consejos de Kerrek SB y use emplace_back
cuando sea posible. Puede ser más rápido (pero a menudo no lo es), puede ser más claro y más compacto, pero también presenta algunas dificultades (especialmente con constructores no explícitos).
Editar , a menudo lo predeterminado es lo que quiere: mover todo lo que se puede mover, copiar el resto. Para pedirlo explícitamente, escriba
A(A && rhs) = default;
Al hacer eso, obtendrá noexcept cuando sea posible: ¿El constructor Mover predeterminado está definido como noexcept?
Tenga en cuenta que las versiones anteriores de Visual Studio 2015 y anteriores no son compatibles, a pesar de que admite la semántica de movimiento.
Parece que la única forma (para C ++ 17 y principios) de imponer el uso de std::vector
semántica de movimiento en la reasignación es eliminar el constructor de copia :). De esta forma usará tus constructores de movimientos o morirá en el intento, en tiempo de compilación :).
Hay muchas reglas donde std::vector
NO DEBE usar move constructor en reasignación, pero nada acerca de dónde DEBE USARLO .
template<class T>
class move_only : public T{
public:
move_only(){}
move_only(const move_only&) = delete;
move_only(move_only&&) noexcept {};
~move_only() noexcept {};
using T::T;
};
o
template<class T>
struct move_only{
T value;
template<class Arg, class ...Args, typename = std::enable_if_t<
!std::is_same_v<move_only<T>&&, Arg >
&& !std::is_same_v<const move_only<T>&, Arg >
>>
move_only(Arg&& arg, Args&&... args)
:value(std::forward<Arg>(arg), std::forward<Args>(args)...)
{}
move_only(){}
move_only(const move_only&) = delete;
move_only(move_only&& other) noexcept : value(std::move(other.value)) {};
~move_only() noexcept {};
};
Su clase T
debe tener noexcept
move constructor / assigment operator y noexcept
destructor. De lo contrario, obtendrás un error de compilación.
std::vector<move_only<MyClass>> vec;