Move semantica in C ++ - Move-return delle variabili locali

8

La mia comprensione è che in C ++ 11, quando si restituisce una variabile locale da una funzione in base al valore, il compilatore può trattare quella variabile come riferimento di valore r e "spostarla" fuori dalla funzione per tornare (se RVO / NRVO non succede invece, ovviamente).

La mia domanda è, non può rompere il codice esistente?

Considera il seguente codice:

#include <iostream>
#include <string>

struct bar
{
  bar(const std::string& str) : _str(str) {}
  bar(const bar&) = delete;
  bar(bar&& other) : _str(std::move(other._str)) {other._str = "Stolen";}
  void print() {std::cout << _str << std::endl;}

  std::string _str;
};

struct foo
{
  foo(bar& b) : _b(b) {}
  ~foo() {_b.print();}

  bar& _b;
};

bar foobar()
{
  bar b("Hello, World!");
  foo f(b);

  return std::move(b);
}

int main()
{
  foobar();
  return EXIT_SUCCESS;
}

I miei pensieri erano che sarebbe stato possibile per un distruttore di un oggetto locale fare riferimento all'oggetto che viene spostato implicitamente e, quindi, vedere inaspettatamente un oggetto "vuoto". Ho provato a testarlo (vedi link ), ma ho ottenuto il risultato "corretto" senza l'esplicito std::move in foobar() . Suppongo che sia dovuto a NRVO, ma non ho provato a riorganizzare il codice per disattivarlo.

Sono corretto nel senso che questa trasformazione (causando un movimento fuori dalla funzione) avvenga implicitamente e potrebbe rompere il codice esistente?

Aggiorna Ecco un esempio che illustra ciò di cui sto parlando. I seguenti due link sono per lo stesso codice. link - C ++ 03 link - C ++ 11

Se guardi l'output, è diverso.

Quindi, immagino che questa domanda diventi ora, è stata presa in considerazione quando si è aggiunta la mossa implicita allo standard, e si è deciso che era OK aggiungere questo cambio di rottura in quanto questo tipo di codice è abbastanza raro? Mi chiedo anche se eventuali compilatori avviseranno in casi come questo ...

    
posta Bwmat 06.10.2014 - 23:07
fonte

1 risposta

6

Scott Meyers pubblicato su comp.lang.c ++ (agosto 2010) su un problema in cui la generazione implicita di costruttori di mosse potrebbe interrompere gli invarianti di classe C ++ 03:

struct X
{
  // invariant: v.size() == 5
  X() : v(5) {}

  ~X() { std::cout << v[0] << std::endl; }

private:    
  std::vector<int> v;
};

int main()
{
    std::vector<X> y;
    y.push_back(X()); // X() rvalue: copied in C++03, moved in C++0x
}

Qui il problema è che in C ++ 03, X aveva un invariato che il suo membro v aveva sempre 5 elementi. X::~X() ha contato su quell'invarianza, ma il costruttore di mosse appena introdotto si è spostato da v , impostando quindi la sua lunghezza su zero.

Questo è correlato al tuo esempio dato che l'invariant rotto viene rilevato solo nel distruttore di X (come dici tu è possibile che un distruttore di un oggetto locale faccia riferimento all'oggetto che viene spostato implicitamente, e quindi inaspettatamente veda un oggetto vuoto ).

C ++ 11 cerca di raggiungere un equilibrio tra la rottura di parte del codice esistente e l'offerta di ottimizzazioni utili basate sui costruttori di movimento.

Il comitato ha inizialmente deciso che i costruttori di spostamenti e gli operatori di spostamento delle mosse dovrebbero essere generati dal compilatore se non forniti dall'utente.

Quindi ha deciso che questo era effettivamente motivo di allarme e limitava la generazione automatica dei costruttori di mosse e spostava gli operatori di assegnazione in modo tale che è molto meno probabile, anche se non impossibile, che il codice esistente si rompa (ad esempio distruttore definito esplicitamente ).

È allettante pensare che impedire la generazione di costruttori di movimenti impliciti quando è presente un distruttore definito dall'utente sia sufficiente ma non è vero ( N3153 - Il Move implicito deve andare per ulteriori dettagli).

In N3174 - Spostare o non spostare Stroupstrup dice:

I consider this a language design problem, rather than a simple backwards compatibility problem. It is easy to avoid breaking old code (e.g. just remove move operations from C++0x), but I see making C++0x a better language by making move operations pervasive a major goal for which it may be worth breaking some C++98 code.

    
risposta data 15.11.2014 - 15:51
fonte

Leggi altre domande sui tag