15 Şubat 2018 Perşembe

std::move

Giriş
std::move verilen l-value nesneyi static_cast ile r_value haline getirir. Kodu şuna benzer.
template <typename T>
typename remove_reference<T>::type&& move(T&& arg) {
  return static_cast<typename remove_reference<T>::type&&>(arg);
}
Yani std::move düşünülenin aksine memcpy, memmove gibi gerçekten taşıma veya kopyalama işlemi yapmaz. Sadece r-value parametre alan bir metodun tetiklenmesini sağlar. Niçin remove_reference yapıldığı burada açıklanıyor.

Örnek
Şöyle yaparız. std::move gerçekte move constructor'ı çağırmadığı için MaybeConsume metodu false dönerse a nesnesi halen kullanılabilir.
// Returns true exactly when ownership of a is taken
bool MaybeConsume(A&& a) {
  if (some condition) {
    Consume(std::move(a));  //Burada move constructor çağrılır
    return true;
  }
  return false;
}

// ... elsewhere ...

A a;
if (!MaybeConsume(std::move(a))) {
  a.DoSomething();  // Burada false döndüğü için a nesnesi kullanılabilir.
}
Primitive Tipler
Ben move işlemini kolay hatırlamak için sadece heap üzerindeki veri ile kullanılır diye düşünüyorum. Bu aslında tam olarak doğru değil ancak anlamayı kolaylaştırıyor. Dolayısıyla bir int'i başka bir int'e move etmenin yolu yok.

Örnek
Elimizde şöyle bir kod olsun. Burada std::move yerine static_cast<int&&> kullanılıyor. Aslında aynı şeyler. Sonuç olarak a ve b değişkenlerini farklı adreslere işaret ettikleri görülebilir.
int a = 1;
int b = static_cast<int&&>(a);
cout << hex << &a << endl;
cout << hex << &b << endl;
Açıklaması şöyle
Just because you "move" them doesn't mean they will share the same address. Moving a value is a high level abstraction, with basic types like int moving and copying is completely the same, which is happening here.
Burada aslında olan şey şöyle.Yani b a'nın move edilmesi ile oluşan rvalue ile ilkleniyor.
No, b is its own object, which is copy initialized from an rvalue reference to another int. This is the same as just copying the referenced object.
For a type like an integer, it's still a plain copy.

r-value alan metod
r-value alan metod parametreyi yeni bir nesneye atamalı. Atama işlemi move constructor'ın tetiklenmesine sebep olur. Move işleminden sonra kaynak nesne artık valid fakat boş bir durumdadır.
Unless otherwise specified, all standard library objects that have been moved from are placed in a valid but unspecified state. That is, only the functions without preconditions, such as the assignment operator, can be safely used on the object after it was moved from
Örnek
Dolayısıyla kaynak nesneyi artık kullanmamak gerekir. Aşağıdaki kod hatalıdır.
vector<string> v_string;
string example = "example";
v_string.push_back(move(example));
cout << example << endl;
Örnek
Aşağıdaki kod r-value parametreyi yeni bir nesneye atamadığı için beklenen sonucu vermiyor. unlock() çağrısı bitiminde mutex halen kilitli olarak kalıyor.
#include <mutex>

void unlock(std::unique_lock<std::mutex> && ulock)
{
}

int main(void)
{
  std::mutex m;
  std::unique_lock<std::mutex> ulock(m);

  unlock(std::move(ulock));

  if (ulock.mutex() == &m || ulock.owns_lock())
  {
    throw std::runtime_error("");
  }

  return 0;
}
std::move ile return etmek
std::move ile return etmek faydasız olabiliyor. Aşağıdaki örnekte moveOn() metodunun sonucunda X nesnesinin move constructor metodu çağrılıyor.
struct X {
    X() = default;
    X(X&& other) { std::cout << "move ctor\n"; }
    X(X const&) = delete;
    void log(std::string const& s){ std::cout << "log: " << s << "\n"; }
};

void sink(X&& x) { 
    x.log("sink"); 
}

X&& passOn(X&& in) {
    in.log("pass");
    return std::move(in);
}

X moveOn(X&& in) {
    in.log("move");
    return std::move(in);
}

int main() {
    sink(passOn(X()));
    std::cout << "===============================\n";
    sink(moveOn(X()));
}
Çıktı olarak şunu alırız.
log: pass
log: sink
===============================
log: move
move ctor
log: sink

passOn metodu xvalue oluşmasına sebep olur. 

Hiç yorum yok:

Yorum Gönder