Perché possiamo usare `std :: move` su un oggetto` const`?


113

In C ++ 11 possiamo scrivere questo codice:

struct Cat {
   Cat(){}
};

const Cat cat;
std::move(cat); //this is valid in C++11

quando chiamo std::move, significa che voglio spostare l'oggetto, cioè cambierò l'oggetto. Spostare un constoggetto è irragionevole, quindi perché std::movenon limitare questo comportamento? Sarà una trappola in futuro, giusto?

Qui trap significa come Brandon ha menzionato nel commento:

"Penso che voglia dire che" intrappola "lui subdolo subdolo perché se non si rende conto, finisce con una copia che non è quello che intendeva."

Nel libro "Effective Modern C ++" di Scott Meyers, fornisce un esempio:

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::movefosse vietato operare su un constoggetto, potremmo facilmente scoprire il bug, giusto?


2
Ma prova a spostarlo. Prova a cambiare il suo stato. std::movedi per sé non fa nulla per l'oggetto. Si potrebbe sostenere che std::moveabbia un nome sbagliato.
juanchopanza

3
In realtà non muove nulla. Tutto ciò che fa è cast su un riferimento rvalue. provare CAT cat2 = std::move(cat);, supponendo che CATsupporti l'assegnazione regolare delle mosse.
WhozCraig

11
std::moveè solo un cast, in realtà non muove nulla
Red Alert

2
@ WhozCraig: attento, poiché il codice che hai pubblicato viene compilato ed eseguito senza preavviso, rendendolo in qualche modo fuorviante.
Mooing Duck

1
@ MoooingDuck Non ha mai detto che non si sarebbe compilato. funziona solo perché il copy-ctor predefinito è abilitato. Squelch quello e le ruote cadono.
WhozCraig

Risposte:


55
struct strange {
  mutable size_t count = 0;
  strange( strange const&& o ):count(o.count) { o.count = 0; }
};

const strange s;
strange s2 = std::move(s);

qui vediamo un uso di std::movesu a T const. Restituisce un file T const&&. Abbiamo un costruttore di mosse strangeche accetta esattamente questo tipo.

E si chiama.

Ora, è vero che questo strano tipo è più raro dei bug che la tua proposta risolverebbe.

Ma, d'altra parte, l'esistente std::movefunziona meglio nel codice generico, dove non sai se il tipo con cui stai lavorando è a To a T const.


3
+1 per essere la prima risposta che in realtà tentativi di spiegare perché si desidera chiamare std::movesu un constoggetto.
Chris Drew

+1 per mostrare l'assunzione di una funzione const T&&. Questo esprime un "protocollo API" del tipo "prenderò un rvalue-ref ma prometto che non lo modificherò". Immagino che, a parte quando si usa mutabile, sia raro. Forse un altro caso d'uso è quello di poterlo usare forward_as_tuplesu quasi tutto e usarlo in seguito.
F Pereira

107

C'è un trucco qui che stai trascurando, vale a dire che in std::move(cat) realtà non muove nulla . Dice semplicemente al compilatore di provare a spostarsi. Tuttavia, poiché la tua classe non ha un costruttore che accetta a const CAT&&, userà invece il const CAT&costruttore di copia implicito e copierà in modo sicuro. Nessun pericolo, nessuna trappola. Se il costruttore di copia è disabilitato per qualsiasi motivo, riceverai un errore del compilatore.

struct CAT
{
   CAT(){}
   CAT(const CAT&) {std::cout << "COPY";}
   CAT(CAT&&) {std::cout << "MOVE";}
};

int main() {
    const CAT cat;
    CAT cat2 = std::move(cat);
}

stampe COPY, no MOVE.

http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f

Nota che il bug nel codice che menzioni è un problema di prestazioni , non un problema di stabilità , quindi un tale bug non causerà un arresto anomalo, mai. Userà solo una copia più lenta. Inoltre, un tale bug si verifica anche per oggetti non const che non hanno costruttori di mosse, quindi la semplice aggiunta di un constsovraccarico non li cattura tutti. Potremmo verificare la capacità di spostare il costrutto o spostare l'assegnazione dal tipo di parametro, ma ciò interferirebbe con il codice del modello generico che dovrebbe ricadere sul costruttore della copia. E diamine, forse qualcuno vuole essere in grado di costruire const CAT&&, da chi sono io per dire che non può?


Upticked. Vale la pena notare che la cancellazione implicita del copy-ctor sulla definizione definita dall'utente del regolare costruttore di mosse o operatore di assegnazione lo dimostrerà anche tramite una compilazione interrotta. Bella risposta.
WhozCraig

Vale anche la pena ricordare che anche un costruttore di copie che necessita di un valore constdiverso non aiuta. [class.copy] §8: "Altrimenti, il costruttore della copia dichiarato implicitamente avrà la forma X::X(X&)"
Deduplicator

9
Non credo che intendesse "trappola" in termini di computer / assemblaggio. Penso che voglia dire che lo "intrappola" in modo subdolo perché se non si rende conto, finisce con una copia che non è ciò che intendeva. Immagino ...
Brandon

potresti ottenere 1 voto in più, e io ne riceverò altri 4, per un distintivo d'oro. ;)
Yakk - Adam Nevraumont,

Dammi il cinque in quel codice di esempio. Ottiene il punto molto bene.
Brent scrive il codice il

20

Uno dei motivi per cui il resto delle risposte finora ha trascurato è la capacità del codice generico di essere resiliente di fronte al movimento. Ad esempio, diciamo che volevo scrivere una funzione generica che spostasse tutti gli elementi da un tipo di contenitore per creare un altro tipo di contenitore con gli stessi valori:

template <class C1, class C2>
C1
move_each(C2&& c2)
{
    return C1(std::make_move_iterator(c2.begin()),
              std::make_move_iterator(c2.end()));
}

Fantastico, ora posso creare in modo relativamente efficiente un vector<string>da a deque<string>e ogni individuo stringverrà spostato nel processo.

Ma cosa succede se voglio passare da un map?

int
main()
{
    std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
    for (auto const& p : v)
        std::cout << "{" << p.first << ", " << p.second << "} ";
    std::cout << '\n';
}

Se si std::moveinsiste su un non- constargomento, l'istanza di cui sopra move_eachnon verrebbe compilata perché sta cercando di spostare un const int(il key_typedi map). Ma a questo codice non importa se non può spostare il file key_type. Vuole spostare la mapped_type( std::string) per motivi di prestazioni.

È per questo esempio, e innumerevoli altri esempi come questo nella codifica generica, che std::moveè una richiesta di spostamento , non una richiesta di spostamento.


2

Ho la stessa preoccupazione dell'OP.

std :: move non sposta un oggetto, né garantisce che l'oggetto sia mobile. Allora perché si chiama mossa?

Penso che non essere mobile possa essere uno dei seguenti due scenari:

1. Il tipo in movimento è const.

Il motivo per cui abbiamo la parola chiave const nel linguaggio è che vogliamo che il compilatore impedisca qualsiasi modifica a un oggetto definito come const. Dato l'esempio nel libro di Scott Meyers:

    class Annotation {
    public:
     explicit Annotation(const std::string text)
     : value(std::move(text)) // "move" text into value; this code
     {  } // doesn't do what it seems to!    
     
    private:
     std::string value;
    };

Cosa significa letteralmente? Spostare una stringa const nel membro del valore, almeno questa è la mia comprensione prima di leggere la spiegazione.

Se il linguaggio intende non spostarsi o non garantire che lo spostamento sia applicabile quando viene chiamato std :: move (), allora è letteralmente fuorviante quando si usa la parola spostamento.

Se il linguaggio incoraggia le persone che usano std :: move ad avere una migliore efficienza, deve prevenire le trappole come questa il prima possibile, specialmente per questo tipo di ovvia contraddizione letterale.

Sono d'accordo che le persone dovrebbero essere consapevoli che spostare una costante è impossibile, ma questo obbligo non dovrebbe implicare che il compilatore possa tacere quando si verifica un'ovvia contraddizione.

2. L'oggetto non ha un costruttore di spostamenti

Personalmente, penso che questa sia una storia separata dalle preoccupazioni di OP, come ha detto Chris Drew

@hvd Mi sembra un po 'un non-argomento. Solo perché il suggerimento di OP non risolve tutti i bug nel mondo non significa necessariamente che sia una cattiva idea (probabilmente lo è, ma non per il motivo che dai). - Chris Drew


1

Sono sorpreso che nessuno abbia menzionato l'aspetto della compatibilità con le versioni precedenti di questo. Credo, è std::movestato appositamente progettato per farlo in C ++ 11. Immagina di lavorare con una base di codice legacy, che si basa fortemente sulle librerie C ++ 98, quindi senza il fallback sull'assegnazione della copia, lo spostamento potrebbe rompere le cose.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.