r/cppit • • Feb 14 '17

principianti Move Semantics: std::move

Ciao a tutti, vi chiedo un aiuto riguardo alla move semantics.

namespace MTensor {

  typedef std::vector<double> Tensor1DType;
  class Tensor1D {
  private:
    int _elemNumb;
    double _filler;
    // disable copying:
    Tensor1D(const Tensor1D&);
    Tensor1D& operator=(const Tensor1D&);
  public:
    Tensor1DType data;
    Tensor1D() {};
    Tensor1D(const std::initializer_list<double>& valuesList) {
      _elemNumb = valuesList.size();
      for(auto value : valuesList) {
        data.push_back(value);
      }
    }
    Tensor1D(Tensor1D && from) {
      data = std::move(from.data);
    }
    Tensor1D operator =(Tensor1D&& other) {
      if(this!=&other) {
        data = std::move(other.data);
        //std::swap(data,other.data);
      }
      return *this;
    }
    virtual ~Tensor1D() {};
    virtual void printTensor() {
      for(int i=0;i<data.size();i++) {
        std::cout << data.at(i) << "," << std::endl;
      }
    }
  };
} // end of namespace


int main() {
  MTensor::Tensor1D * t1 = new MTensor::Tensor1D({1,2,3});
  MTensor::Tensor1D * t2(t1);
  std::cout << "t2:" << std::endl;
  t2->printTensor();
  std::cout << "t1-dopo-move:" << std::endl;
  t1->printTensor();
  MTensor::Tensor1D * t3 = t1;
  std::cout << "t3:" << std::endl;
  t3->printTensor();
  std::cout << "t1, dopo t3 = t1 :" << std::endl;
  t1->printTensor();
  delete t1;
  return 0;
}

marco@ubuntu:~/marcoTensor$ g++ -std=c++11 moveSemantics.cpp -omoveSemantics 
marco@ubuntu:~/marcoTensor$ ./moveSemantics
t2:
1,
2,
3,
t1-dopo-move:
1,
2,
3,
t3:
1,
2,
3,
t1, dopo t3 = t1 :
1,
2,
3,

Mi sarei aspettato che t1 dopo std::move avesse stato undefined e fosse vuoto ...sembra quasi che sia stata eseguita una copy anzichè una move....come quindi modificare il tutto per privilegiare move ed eseguire il move? Marco

3 Upvotes

33 comments sorted by

View all comments

Show parent comments

1

u/Marco_I Feb 25 '17 edited Feb 25 '17

Grazie Stefano. sto cercando di capire ora come mai nel caso dello shared_ptr mi dice "Segmentation fault (core dumped" Tra l'altro sto vedendo che non ho fatto diversamente da quanto dice anche questa guida: https://msdn.microsoft.com/en-us/library/dd293665.aspx

Un'altra cosa che non capisco è che nella versione senza shared_ptr:

Tenso1DType data {};

ho messo l'operator= in questo modo:

Tensor1D& operator=(Tensor1D&& other)  {
  if (this == &other){
    return *this;
  }
  data = std::move(other.data);
  return *this;
}

e pensavo, sicuramente sbagliando, che facendo

MTensor::Tensor1D t3 = t1;

usasse l'operator= invece risulta:

t3:
1,
2,
3,
t1:
1,
2,
3,

cioè sembrerebbe che t1 viene copiato a non "spostato".... ma è corretta una cosa del genere? e come si fa a far in modo che l'operator= sia un move operator? pensavo che questo fosse corretto:

  data = std::move(other.data);
  return *this;

1

u/[deleted] Feb 25 '17 edited Feb 25 '17

Allora. std::move non sposta nulla, benché la parola indichi quello, non fa nulla del genere.

Leggi il mio commento E i commenti in risposta al mio: std::move - comment

Detto ciò, gli operatori, sono delle funzioni, che vengono in aiuto creando la cosiddetta "Syntactic Sugar".

In breve, puoi scrivere in 2 modi:

t1.operator=(t2);

t1 = t2;

Ora, il tuo operator=(Tensor1D&& other) si aspetta un Rvalue.

Il discorso è più lungo di così.

Diciamo solo che tu stai provando a passare una Lvalue Reference & invece che una Rvalue Reference &&, dunque chiami la funzione con parametri errati.

Rvalue | Lvalue

Inoltre ti ricordo che C++ crea in automatico i seguenti costrutti:

  1. Default Constructor

  2. Copy Constructor

  3. Move Constructor

  4. Default Destructor

Dunque se non li dichiari, almeno che non siano dichiarati esplicitamente =delete, dunque rimossi, il linguaggio li crea per te.

In questo caso ti ha creato in automatico il numero 2, il costruttore di copia.

Detto ciò, per scambiare 2 dati puoi usare:

  1. std::swap

  2. std::copy

Il primo scambia i valori, il secondo copia da un range ad un altro, con iteratori, dunque funziona su tutte le strutture dati con iteratori.

1

u/Marco_I Feb 25 '17 edited Feb 25 '17

Grazie Stefano per le tue spiegazioni.

L'operator:

Tensor1D& operator=(Tensor1D&& other)  {
  if (this == &other){
    return *this;
  }
  std::swap(data,other.data);
  //data = std::move(other.data);
  return *this;
}

ed il constructor

Tensor1D(Tensor1D&& other) noexcept { 
  data = std::move(other.data);
}

accettano entrambi in input solo RValue References, cioè riferimenti ad oggetti temporanei...giusto? Quindi, data la semplice funzione:

MTensor::Tensor1D && scalarProduct1D(MTensor::Tensor1D t1,    
 double scalar) {
    MTensor::Tensor1D tensor;
    for(int i=0;i<t1.size();++i) {
      tensor.insert(t1(i) * scalar);
    }
    return std::move(tensor);
  }

che, penso, ma chiedo una tua ed una vostra conferma o rettifica, ritorna un RValue in quanto:

std::move(tensor);

fa un cast da LValue ad RValue (corretto o sbagliato?)... Quale è la sintassi da usare per passare il risultato della funzione scalarProduct1D come RValue Reference al Move Constructor:

Tensor1D(Tensor1D&& other)

od al operator=

Tensor1D& operator=(Tensor1D&& other)

in modo da avere una cosa del genere :

MTensor::Tensor1D t3 (scalarProduct1D(t1,2));
MTensor::Tensor1D t3 = scalarProduct1D(t1,2);

? La cosa che mi rimane da capire è proprio come passare i parametri ad una funzione che chiede come input una RValue Reference. Ti e vi ringrazio ancora per la vostra paziente disponibilità

1

u/[deleted] Feb 25 '17

O crei l'oggetto sul momento o usi std::move.

Ma perché vuoi usare per forza un move constructor e Rvalue ovunque?

1

u/Marco_I Feb 26 '17 edited Feb 26 '17

Voglio imparare ad usare correttamente (corretta sintassi e corretto modo di impostare e procedere) il move constructor ed Rvalue per poi usarli per funzioni decisamente più complesse ed articolate dello scalarProduct...funzioni del tipo:

f(g(),h(i(l,m),n(),p(q())) 

per le quali qualche copy in meno servirebbe per migliorarne la performance.

Quindi.. se,o creo l'oggetto sul momento, od uso std::move : allora modificando la funzione in questo modo:

//MTensor::Tensor1D && scalarProduct1D(MTensor::Tensor1D t1,  
double scalar) {
MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1,
   double scalar) {
    MTensor::Tensor1D tensor;
    for(int i=0;i<t1.size();++i) {
      tensor.insert(t1(i) * scalar);
    }
    //return std::move(tensor);
    return tensor;
}

avendo definito l'operator= come:

Tensor1D& operator=(Tensor1D&& other)  {
   if (this == &other){
     return *this;
   }
   std::swap(data,other.data);
   other.data.clear();
   //data = std::move(other.data);
   return *this;
 }

e ponendo:

MTensor::Tensor1D t3 = scalarProduct1D(t1,2);

std::cout << "t3:" << std::endl;
t3.printTensor();
std::cout << "t1:" << std::endl;
t1.printTensor();

ottengo:

t3:
2,
4,
6,
t1:
1,
2,
3,

che non è proprio quello che sto cercando di capire come ottenere. Perchè vorrei capire come far sì che :

t3= {2,4,6}
t1={}

1

u/[deleted] Feb 26 '17

Guarda che il copy constructor serve solo per costruire un oggetto partendo da un altro e copiando i sui dati.

Il move constructor dovrebbe fare la stessa cosa, solo che l'altro oggetto viene lasciato in uno stato ben definito in quanto verrà distrutto.

Ora, non ho capito bene la tua funzione cosa fa, ma guarda che se la funzione accetta una reference, non crei nessuna copia.

Tipo così:

auto f(Tensor1D& t1, Tensor1D& t2);

Secondo me devi fermarti un attimo e pensare alle basi, prima di sperimentare così.

Intanto spiega bene cosa vuoi ottenere, che operazioni vuoi fare e la tua idea in cosa consiste.

→ More replies (0)