Tengo varios std::vector
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 relleno el vector con A
objetos (usando por ejemplo myvec.push_back(a)
), el vector crecerá de tamaño, usando el constructor de copia A( const A&)
para instanciar nuevas copias de los elementos en el vector.
¿Puedo hacer cumplir de alguna manera que el constructor de movimiento de la clase se A
esté usando en su lugar?
Respuestas:
Debe informar a C ++ (específicamente
std::vector
) que su constructor de movimiento y su destructor no arroja, usandonoexcept
. Luego, se llamará al constructor de movimiento cuando el vector crezca.Así es como declarar e implementar un constructor 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 lo es
noexcept
, nostd::vector
puede usarlo, ya que entonces no puede asegurar 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 C ++ Move semántica y excepciones
Gracias a Bo, quien insinuó que puede tener que ver con excepciones. También considere los consejos de Kerrek SB y utilícelo
emplace_back
cuando sea posible. Se puede ser más rápido (pero a menudo no lo es), que puede ser más clara y compacta, pero también hay algunas trampas (especialmente con los constructores no explícitas).Edite , a menudo lo predeterminado es lo que desea: mueva todo lo que se pueda mover, copie el resto. Para pedirlo explícitamente, escriba
A(A && rhs) = default;
Al hacer eso, obtendrá noexcepto cuando sea posible: ¿El constructor Move predeterminado está definido como noexcept?
Tenga en cuenta que las versiones anteriores de Visual Studio 2015 y anteriores no lo admitían, aunque admite la semántica de movimiento.
fuente
value_type
's movimiento ctor esnoexcept
? ¿Quizás el lenguaje restringe el conjunto de candidatos a la llamada de función cuando el alcance de la llamada también es unanoexcept
función?noexcept
constructor de movimientos.is_nothrow_move_constructible
será verdadero si hay unnothrow
constructor de copia. No conozco ningún caso real denothrow
constructores de copias costosos, por lo que no está claro que realmente importe.noexcept
tanto en el encabezado como en la implementación, y cuando hago un push_back (std:; move) todavía llama al constructor de copia. Me estoy arrancando el pelo aquí.std::move()
en lapush_back()
llamada equivocada . Uno de esos momentos en los que busca un problema con tanta atención que no ve el error obvio justo frente a usted. Y luego llegó la hora del almuerzo y olvidé borrar mi comentario.Curiosamente, el vector de gcc 4.7.2 solo usa el constructor de movimiento si tanto el constructor de movimiento como el destructor lo 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 da como resultado lo esperado:
Sin embargo, cuando elimino
noexcept
de~foo()
, el resultado es diferente:Supongo que esto también responde a esta pregunta .
fuente
Parece que la única forma (para C ++ 17 y versiones anteriores) de hacer cumplir la
std::vector
semántica de uso de movimiento en la reasignación es eliminar el constructor de copia :). De esta manera, usará sus constructores de movimientos o morirá en el intento, en tiempo de compilación :).Hay muchas reglas en las que
std::vector
NO DEBE usar move constructor en la reasignación, pero nada sobre 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; };
En Vivo
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 {}; };
Código en vivo
Su
T
clase debe tener unnoexcept
constructor de movimiento / operador de asignación y unnoexcept
destructor. De lo contrario, obtendrá un error de compilación.std::vector<move_only<MyClass>> vec;
fuente