Como aplicar a semântica de movimento quando um vetor cresce?

92

Eu tenho uma std::vectorsérie de objetos de uma certa classe A. A classe não é trivial e tem construtores de cópia e construtores de movimento definidos.

std::vector<A>  myvec;

Se eu preencher o vetor com Aobjetos (usando por exemplo myvec.push_back(a)), o vetor aumentará de tamanho, usando o construtor de cópia A( const A&)para instanciar novas cópias dos elementos no vetor.

Posso de alguma forma impor que o construtor de movimento da classe Aestá começando a ser usado em vez disso?

Bertwim van Beest
fonte
5
Você pode, usando uma implementação de vetor com reconhecimento de movimento.
K-ballo
2
Você pode ser um pouco mais específico sobre como conseguir isso?
Bertwim van Beest
1
Você simplesmente usa uma implementação de vetor com reconhecimento de movimento. Parece que a implementação da sua biblioteca padrão (o que é mesmo?) Não reconhece movimento. Você pode tentar com contêineres com reconhecimento de movimentação do Boost.
K-ballo
1
Bem, eu uso o gcc 4.5.1, que reconhece movimentos.
Bertwim van Beest
No meu código, funcionou para tornar o construtor de cópia privado, mesmo que o construtor de movimento não tivesse o "noexcept" explícito.
Arne de

Respostas:

126

Você precisa informar ao C ++ (especificamente std::vector) que o seu construtor e destruidor de movimento não atira, usando noexcept. Então, o construtor de movimento será chamado quando o vetor crescer.

Esta é a forma de declarar e implementar um construtor de movimento que é respeitado 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...
}

Se o construtor não for noexcept, não std::vectorpode usá-lo, pois então não pode garantir as garantias de exceção exigidas pelo padrão.

Para mais informações sobre o que é dito no padrão, leia C ++ Move semantics and Exceptions

Crédito para Bo, que deu a entender que pode ter a ver com exceções. Considere também o conselho de Kerrek SB e use emplace_backquando possível. Ele pode ser mais rápido (mas muitas vezes não é), pode ser mais clara e mais compacto, mas também existem algumas armadilhas (especialmente com construtores não explícitas).

Edite , muitas vezes o padrão é o que você deseja: mova tudo o que pode ser movido, copie o resto. Para pedir isso explicitamente, escreva

A(A && rhs) = default;

Fazendo isso, você obterá noexcept quando possível: O construtor Move padrão está definido como noexcept?

Observe que as versões anteriores do Visual Studio 2015 e anteriores não davam suporte a isso, embora ofereça suporte à semântica de movimentação.

Johan Lundberg
fonte
Fora de interesse, como faz a impl "saber" se o value_type's ctor movimento é noexcept? Talvez a linguagem restrinja o conjunto de candidatos de chamada de função quando o escopo de chamada também é uma noexceptfunção?
Lightness Races in Orbit
1
@LightnessRacesinOrbit Presumo que esteja apenas fazendo algo como en.cppreference.com/w/cpp/types/is_move_constructible . Pode haver apenas um construtor de movimento, portanto, ele deve ser claramente definido pela declaração.
Johan Lundberg
@LightnessRacesinOrbit, aprendi desde então que não existe uma maneira (padrão / útil) de realmente saber se existe um noexceptconstrutor de movimento. is_nothrow_move_constructibleserá verdadeiro se houver um nothrowconstrutor de cópia. Não tenho conhecimento de nenhum caso real de nothrowconstrutores de cópias caros, então não está claro se isso realmente importa.
Johan Lundberg
Não funciona para mim. Minhas funções de destruidor, construtor de movimento e atribuição de movimentação são marcadas noexceptno cabeçalho e na implementação e, quando faço um push_back (std:; move), ele ainda chama o construtor de cópia. Estou arrancando meu cabelo aqui.
AlastairG
1
@Johan eu encontrei o problema. Eu estava usando std::move()a push_back()chamada errada . Uma daquelas vezes em que você está procurando tanto por um problema que não vê o erro óbvio bem na sua frente. E então era hora do almoço e esqueci de deletar meu comentário.
AlastairG
17

Curiosamente, o vetor do gcc 4.7.2 só usa o construtor de movimento se o construtor de movimento e o destruidor o forem noexcept. Um exemplo simples:

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();
}

Isso produz o esperado:

move
move
move

No entanto, quando removo noexceptde ~foo(), o resultado é diferente:

copy
copy
copy

Eu acho que isso também responde a essa pergunta .

Nikola Benes
fonte
Parece-me que as outras respostas falam apenas sobre o construtor de movimento, não sobre o destruidor não ter exceto.
Nikola Benes
Bem, deveria ser, mas ao que parece, no gcc 4.7.2 não era. Portanto, esse problema era, na verdade, específico do gcc. No entanto, ele deve ser corrigido no gcc 4.8.0. Veja a questão stackoverflow relacionada .
Nikola Benes
-1

Parece que a única maneira (para C ++ 17 e anteriores) de forçar o std::vectoruso da semântica de movimentação na realocação é excluindo o construtor de cópia :). Desta forma, ele usará seus construtores de movimento ou teste de dados, em tempo de compilação :).

Existem muitas regras onde std::vectorNÃO DEVE usar o construtor de movimento na realocação, mas nada sobre onde ele DEVE USAR .

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;   
};

Viver

ou

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 ao vivo

Sua Tclasse deve ter noexcepto construtor de movimento / operador de atribuição e noexceptdestruidor. Caso contrário, você obterá um erro de compilação.

std::vector<move_only<MyClass>> vec;
torre 120
fonte
1
Não é necessário excluir o construtor de cópia. Se o construtor de movimento não for exceto, ele será usado.
balki
@balki PODE ser usado. Padrão não REQUER isso agora. Aqui está a discussão groups.google.com/a/isocpp.org/forum/…
tower120 01 de