Pergunta

  1. O que é isso?
  2. O que isso faz?
  3. Quando deve ser usado?

Bons links são apreciados.

Foi útil?

Solução

Página da Wikipedia em referências de valor R C ++ 11 e construtores de movimentos

  1. Em C ++ 11, além de copiar construtores, os objetos podem ter construtores de movimentação.
    (E além dos operadores de atribuição de cópias, eles têm operadores de atribuição de movimentos.)
  2. O construtor de movimentos é usado em vez do construtor de cópias, se o objeto tiver o tipo "rvalue-reference" (Type &&).
  3. std::move() é um elenco que produz uma referência rvalue a um objeto, para permitir a mudança dele.

É uma nova maneira C ++ de evitar cópias. Por exemplo, usando um construtor de movimentação, um std::vector poderia apenas copiar seu ponteiro interno para dados para o novo objeto, deixando o objeto movido em um estado incorreto, evitando copiar todos os dados. Isso seria C ++-válido.

Tente pesquisar no Google para mover semântica, rvalue, encaminhamento perfeito.

Outras dicas

1. "O que é?"

Enquanto std::move() é tecnicamente uma função - eu diria Não é verdade uma função. É meio que um conversor Entre as maneiras pelas quais o compilador considera o valor de uma expressão.

2. "O que isso faz?"

A primeira coisa a notar é que std::move() Na verdade não move nada. Ele converte uma expressão de ser um lvalue (como uma variável nomeada) para ser um xvalue. Um XValue diz ao compilador:

Você pode me saquear, jogada Tudo o que estou segurando e uso em outro lugar (já que vou ser destruído em breve) ".

em outras palavras, quando você usa std::move(x), você está permitindo que o compilador canibalize x. Assim, se x digamos, seu próprio buffer na memória - depois std::move()O compilador pode ter outro objeto o possuir.

Você também pode se mudar de um Prvvalue (como um temporário que você está passando), mas isso raramente é útil.

3. "Quando deve ser usado?"

Outra maneira de fazer essa pergunta é "para que eu canibalizaria os recursos de um objeto existente?" Bem, se você está escrevendo código de aplicativo, provavelmente não estaria brincando muito com objetos temporários criados pelo compilador. Então, principalmente, você faria isso em lugares como construtores, métodos de operador, funções do tipo algoritmo-algoritmo padrão etc. onde os objetos são criados e destruíram muito automaticamente. Claro, isso é apenas uma regra geral.

Um uso típico é os recursos "movendo" de um objeto para outro em vez de copiar. @Guillaume links para esta página que tem um exemplo curto direto: trocar dois objetos com menos cópia.

template <class T>
swap(T& a, T& b) {
    T tmp(a);   // we now have two copies of a
    a = b;      // we now have two copies of b (+ discarded a copy of a)
    b = tmp;    // we now have two copies of tmp (+ discarded a copy of b)
}

O uso do movimento permite trocar os recursos em vez de copiá -los:

template <class T>
swap(T& a, T& b) {
    T tmp(std::move(a));
    a = std::move(b);   
    b = std::move(tmp);
}

Pense no que acontece quando t é, digamos, vector<int> de tamanho n. Na primeira versão que você lê e escreve 3*n elementos, na segunda versão que você basicamente lê e escreve apenas os 3 ponteiros para os buffers dos vetores. Obviamente, a classe T precisa saber como fazer a mudança; Sua aula deve ter um operador de assinatura de movimentação e um construtor de movimentos para a Classe T para que isso funcione.

Você pode usar o Move quando precisar "transferir" o conteúdo de um objeto em outro lugar, sem fazer uma cópia (ou seja, o conteúdo não é duplicado, é por isso que ele pode ser usado em alguns objetos não copiáveis, como um exclusivo). Também é possível que um objeto tome o conteúdo de um objeto temporário sem fazer uma cópia (e economizar muito tempo), com o STD :: Move.

Este link realmente me ajudou:

http://thbecker.net/articles/rvalue_references/section_01.html

Sinto muito se minha resposta está chegando tarde demais, mas eu também estava procurando um bom link para o STD :: Move, e encontrei os links acima um pouco "austero".

Isso coloca a ênfase na referência de valor R, em que contexto você deve usá-los, e acho que é mais detalhado, é por isso que eu queria compartilhar esse link aqui.

Q: O que é std::move?

UMA: std::move() é uma função da biblioteca padrão C ++ para fundição em uma referência de RValue.

Simplista std::move(t) é equivalente a:

static_cast<T&&>(t);

Um rvalue é um temporário que não persiste além da expressão que a define, como um resultado de função intermediária que nunca é armazenada em uma variável.

int a = 3; // 3 is a rvalue, does not exist after expression is evaluated
int b = a; // a is a lvalue, keeps existing after expression is evaluated

Uma implementação para std :: move () é dada em N2027: "Uma breve introdução às referências Rvalue" do seguinte modo:

template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
    return a;
}

Como você pode ver, std::move retorna T&& não importa se chamado com um valor (T), tipo de referência (T&) ou referência de rvalue (T&&).

P: O que isso faz?

A: Como elenco, ele não faz nada durante o tempo de execução. É relevante apenas no momento da compilação dizer ao compilador que você gostaria de continuar considerando a referência como um RValue.

foo(3 * 5); // obviously, you are calling foo with a temporary (rvalue)

int a = 3 * 5;
foo(a);     // how to tell the compiler to treat `a` as an rvalue?
foo(std::move(a)); // will call `foo(int&& a)` rather than `foo(int a)` or `foo(int& a)`

O que faz não Faz:

  • Faça uma cópia do argumento
  • Ligue para o construtor de cópias
  • Alterar o objeto de argumento

P: Quando deve ser usado?

A: Você deve usar std::move Se você deseja chamar funções que suportam mover a semântica com um argumento que não é um RValue (expressão temporária).

Isso levanta as seguintes perguntas de acompanhamento para mim:

  • O que são semântica de movimentos? Mova a semântica, em contraste com a cópia semântica, é uma técnica de programação na qual os membros de um objeto são inicializados por 'assumir o controle' em vez de copiar os membros de outro objeto. Tal 'assumir' faz sentido apenas com ponteiros e alças de recursos, o que pode ser transferido barato copiando o ponteiro ou o identificador inteiro, em vez dos dados subjacentes.

  • Que tipo de classes e objetos suportam mover a semântica? Cabe a você como desenvolvedor implementar a semântica de movimentos em suas próprias classes, se elas se beneficiariam de transferir seus membros em vez de copiá -los. Depois de implementar a semântica do Move, você se beneficiará diretamente do trabalho de muitos programadores de bibliotecas que adicionaram suporte para lidar com aulas com a semântica de movimentos com eficiência.

  • Por que o compilador não pode descobrir por conta própria? O compilador não pode simplesmente chamar outra sobrecarga de uma função, a menos que você diga. Você deve ajudar o compilador a escolher se a versão regular ou mover da função deve ser chamada.

  • Em quais situações eu gostaria de dizer ao compilador que ele deve tratar uma variável como um Rvalue? Isso provavelmente acontecerá nas funções de modelo ou biblioteca, onde você sabe que um resultado intermediário pode ser recuperado.

STD :: Move em si realmente não faz muito. Eu pensei que ele chamou o construtor movido para um objeto, mas ele realmente executa um elenco do tipo (lançando uma variável LValue para um RValue para que a referida variável possa ser passada como um argumento para um construtor de movimentos ou operador de atribuição).

Portanto, o movimento STD :: é usado apenas como precursor para usar a semântica do Move. Mover Semantics é essencialmente uma maneira eficiente de lidar com objetos temporários.

Considere objeto A = B + C + D + E + F;

Este é um código bonito, mas o E + F produz um objeto temporário. Então D + Temp produz outro objeto temporário e assim por diante. Em cada operador normal "+" de uma classe, ocorrem cópias profundas.

Por exemplo

Object Object::operator+ (const Object& rhs) {
    Object temp (*this);
    // logic for adding
    return temp;
}

A criação do objeto temporário nesta função é inútil - esses objetos temporários serão excluídos no final da linha de qualquer maneira, enquanto saem do escopo.

Podemos preferir usar a semântica de mover para "saquear" os objetos temporários e fazer algo como

 Object& Object::operator+ (Object&& rhs) {
     // logic to modify rhs directly
     return rhs;
 }

Isso evita cópias desnecessárias profundas sendo feitas. Com referência ao exemplo, a única parte em que a cópia profunda ocorre agora é E + F. O restante usa a semântica de movimentos. O construtor de movimentos ou operador de atribuição também precisa ser implementado para atribuir o resultado a A.

"O que é isso?" e "O que isso faz?" foi explicado acima.

Vou dar um exemplo de "Quando deve ser usado".

Por exemplo, temos uma aula com muitos recursos como a grande matriz.

class ResHeavy{ //  ResHeavy means heavy resource
    public:
        ResHeavy(int len=10):_upInt(new int[len]),_len(len){
            cout<<"default ctor"<<endl;
        }

        ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
            cout<<"copy ctor"<<endl;
        }

        ResHeavy& operator=(const ResHeavy& rhs){
            _upInt.reset(new int[rhs._len]);
            _len = rhs._len;
            cout<<"operator= ctor"<<endl;
        }

        ResHeavy(ResHeavy&& rhs){
            _upInt = std::move(rhs._upInt);
            _len = rhs._len;
            rhs._len = 0;
            cout<<"move ctor"<<endl;
        }

    // check array valid
    bool is_up_valid(){
        return _upInt != nullptr;
    }

    private:
        std::unique_ptr<int[]> _upInt; // heavy array resource
        int _len; // length of int array
};

Código de teste:

void test_std_move2(){
    ResHeavy rh; // only one int[]
    // operator rh

    // after some operator of rh, it becomes no-use
    // transform it to other object
    ResHeavy rh2 = std::move(rh); // rh becomes invalid

    // show rh, rh2 it valid
    if(rh.is_up_valid())
        cout<<"rh valid"<<endl;
    else
        cout<<"rh invalid"<<endl;

    if(rh2.is_up_valid())
        cout<<"rh2 valid"<<endl;
    else
        cout<<"rh2 invalid"<<endl;

    // new ResHeavy object, created by copy ctor
    ResHeavy rh3(rh2);  // two copy of int[]

    if(rh3.is_up_valid())
        cout<<"rh3 valid"<<endl;
    else
        cout<<"rh3 invalid"<<endl;
}

saída como abaixo:

default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid

Nós podemos ver isso std::move com move constructor faz com que o recurso de transformação facilmente.

Onde mais está std::move útil?

std::move Também pode ser útil ao classificar uma variedade de elementos. Muitos algoritmos de classificação (como classificação de seleção e tipo de bolha) funcionam trocando pares de elementos. Anteriormente, tivemos que recorrer à cópia-semantics para fazer a troca. Agora podemos usar a semântica do Move, o que é mais eficiente.

Também pode ser útil se quisermos mover o conteúdo gerenciado por um ponteiro inteligente para outro.

Citado:

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top