Em C ++ 11, podemos escrever este código:
struct Cat {
Cat(){}
};
const Cat cat;
std::move(cat); //this is valid in C++11
quando eu chamo std::move
, significa que quero mover o objeto, ou seja, vou mudar o objeto. Mover um const
objeto não é razoável, então por que std::move
não restringe esse comportamento? Vai ser uma armadilha no futuro, certo?
Aqui, armadilha significa como Brandon mencionou no comentário:
"Eu acho que ele quis dizer isso" uma armadilha "sorrateiramente, porque se ele não perceber, ele acaba com uma cópia que não é o que ele pretendia."
No livro 'Effective Modern C ++' de Scott Meyers, ele dá um exemplo:
class Annotation {
public:
explicit Annotation(const std::string text)
: value(std::move(text)) //here we want to call string(string&&),
//but because text is const,
//the return type of std::move(text) is const std::string&&
//so we actually called string(const string&)
//it is a bug which is very hard to find out
private:
std::string value;
};
Se std::move
fosse proibido de operar em um const
objeto, poderíamos facilmente descobrir o bug, certo?
std::move
por si só não faz nada para o objeto. Alguém poderia argumentar questd::move
é mal nomeado.CAT cat2 = std::move(cat);
, supondo queCAT
suporte atribuição de movimento regular.std::move
é apenas um elenco, na verdade não move nadaRespostas:
aqui vemos o uso de
std::move
em aT const
. Ele retorna aT const&&
. Temos um construtor de movimento parastrange
que seja exatamente esse tipo.E é chamado.
Agora, é verdade que esse tipo estranho é mais raro do que os bugs que sua proposta consertaria.
Mas, por outro lado, o existente
std::move
funciona melhor em código genérico, onde você não sabe se o tipo com o qual está trabalhando é aT
ou aT const
.fonte
std::move
umconst
objeto.const T&&
. Isso expressa um "protocolo API" do tipo "Vou pegar um rvalue-ref, mas prometo que não vou modificá-lo". Acho que, além de usar mutável, é incomum. Talvez outro caso de uso seja ser capaz de usarforward_as_tuple
em quase tudo e depois usá-lo.Há um truque aqui que você está esquecendo, que
std::move(cat)
não move nada . Apenas diz ao compilador para tentar mover. No entanto, como sua classe não tem um construtor que aceite umconst CAT&&
, ela usará oconst CAT&
construtor de cópia implícito e copiará com segurança. Sem perigo, sem armadilha. Se o construtor de cópia for desativado por qualquer motivo, você receberá um erro do compilador.impressões
COPY
, nãoMOVE
.http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f
Observe que o bug no código que você mencionou é um problema de desempenho , não um problema de estabilidade , então tal bug não causará um travamento, nunca. Só usará uma cópia mais lenta. Além disso, esse bug também ocorre para objetos não constantes que não têm construtores de movimento, portanto, apenas adicionar uma
const
sobrecarga não detectará todos eles. Poderíamos verificar a capacidade de mover a construção ou a atribuição do tipo de parâmetro, mas isso interferiria no código de modelo genérico que deveria recorrer ao construtor de cópia. E, diabos, talvez alguém queira ser capaz de construir a partir deconst CAT&&
quem sou eu para dizer que ele não pode?fonte
const
também não ajudará. [class.copy] §8: "Caso contrário, o construtor de cópia declarado implicitamente terá a formaX::X(X&)
"Um motivo pelo qual o restante das respostas foi negligenciado até agora é a capacidade do código genérico de ser resiliente em caso de mudança. Por exemplo, digamos que eu quisesse escrever uma função genérica que movesse todos os elementos de um tipo de contêiner para criar outro tipo de contêiner com os mesmos valores:
Legal, agora posso criar um a
vector<string>
partir de a com relativa eficiênciadeque<string>
e cada indivíduostring
será movido no processo.Mas e se eu quiser mudar de um
map
?Se
std::move
insistido em um não-const
argumento, a instanciação acima demove_each
não seria compilada porque está tentando mover aconst int
(okey_type
demap
). Mas este código não se importa se ele não pode mover okey_type
. Ele deseja mover omapped_type
(std::string
) por motivos de desempenho.É para este exemplo, e incontáveis outros exemplos como este na codificação genérica, que
std::move
é uma solicitação para mover , não uma demanda para mover.fonte
Tenho a mesma preocupação do OP.
std :: move não move um objeto, nem garante que o objeto seja móvel. Então, por que é chamado de movimento?
Acho que não ser móvel pode ser um dos seguintes dois cenários:
1. O tipo de movimento é constante.
A razão de termos a palavra-chave const na linguagem é que queremos que o compilador evite qualquer alteração em um objeto definido como const. Dado o exemplo no livro de Scott Meyers:
O que significa literalmente? Mova uma string const para o membro de valor - pelo menos, foi o que entendi antes de ler a explicação.
Se a linguagem pretende não se mover ou não garantir que o movimento seja aplicável quando std :: move () é chamado, então é literalmente enganoso ao usar a palavra mover.
Se a linguagem está encorajando as pessoas que usam std :: move a ter melhor eficiência, ela deve evitar armadilhas como essa o mais cedo possível, especialmente para esse tipo de contradição literal óbvia.
Concordo que as pessoas devem estar cientes de que mover uma constante é impossível, mas essa obrigação não deve implicar que o compilador possa ficar em silêncio quando ocorrer uma contradição óbvia.
2. O objeto não tem construtor de movimento
Pessoalmente, acho que esta é uma história diferente da preocupação da OP, como disse Chris Drew
fonte
Estou surpreso que ninguém mencionou o aspecto de compatibilidade com versões anteriores disso. Eu acredito que
std::move
foi projetado propositalmente para fazer isso em C ++ 11. Imagine que você está trabalhando com uma base de código legada, que depende fortemente de bibliotecas C ++ 98, portanto, sem o fallback na atribuição de cópia, mover quebraria as coisas.fonte
Felizmente, você pode usar a verificação do clang-tidy para encontrar esses problemas: https://clang.llvm.org/extra/clang-tidy/checks/performance-move-const-arg.html
fonte