22 Mart 2019 Cuma

Move Constructor

Giriş
C++11'den önce move semantics adına yapılan işlem std::swap () kullanımıdır. emplace_back() için şöyle yapılırdı.
class MyListOfVectors {
  private:
    //using `std::vector<int>` as an example of a "movable" type.
    std::vector<std::vector<int>> list;
  public:
    void emplace_back(std::vector<int> &n) {
        using std::swap;
        list.push_back(std::vector<int>());
        swap(list.back(), n);
        //(possibly add something to rollback the `push`
        // in case swap fails; to provide the strong
        // exception guarantee)
    }
};
pop_back() için şöyle yapılırdı.
std::vector<int> MyListOfVectors::pop_back() {
  using std::swap;
  std::vector<int> r;
  swap(list.back(), r);
  list.pop_back();
  return r; //Trust in copy elision to avoid any actual copies.
}
Nasıl Tanımlanır
Örnek
Şöyle yaparız.
Foo (Foo&& other)
: _x(std::move(other._x))
{}
Örnek
Yanlış tanımlama ise şöyle.
Foo (const Foo &&other) {
 ...
}
Açıklaması şöyle
You cannot move from a const value because moving implies modifying what you're moving from.

Nasıl Çağrılır
Move constructor sağ taraf rvalue ise çağrılır. Eğer sağ taraf lvalue ise copy constructor çağrılır.
Örnekte copy constructor kullanılır.
A a;
A b = a;
b.foo();
Eğer move constructor çağrılmasını istiyorsak şöyle yaparız.
A a("a");
A b = std::move(a);
b.foo();
Üretilme Koşulları
Şu koşullarda üretilmez.
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
  • X does not have a user-declared copy constructor,
  • X does not have a user-declared copy assignment operator,
  • X does not have a user-declared move assignment operator,
  • X does not have a user-declared destructor, and
  • the move constructor would not be implicitly defined as deleted.
[ Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that otherwise would have invoked the move constructor may instead invoke a copy constructor. — end note ]
Aynı açıklama şöyle
The move constructor is auto-generated if there is no user-declared copy constructor, copy assignment operator or destructor, and if the generated move constructor is valid (e.g. if it wouldn't need to assign constant members) (§12.8/10).
Şeklen şöyledir.

1. Madde Copy Constructor Varsa
Sınıfın copy constructor metodu varsa üretilmez.

Örnek
Copy constructor delete olarak işaretli olsa bile varmış gibi muamele görür.
class MyType {
public:
    MyType(std::array<double, 6> a) {}
    MyType(const MyType& that) = delete;
};
Örnek
Şu kod derlenmez.
template <typename T>
struct Container
{
  Container() = default;
  Container(const Container& other) = delete;
  Container(T* ptr) : ptr(ptr) {}
  T* ptr;
  ~Container() { delete ptr; }

};

struct Foo { Foo(int a, int b) {} };

std::vector<Container<Foo>> myvector;
myvector.emplace_back((new Foo(1, 2))); //move constructor is not generated
Örnek
Sınıfın move consructor metodu üretilmezse bile sınıf halen move edilebilir. Copy constructor metodunun olması yeterli. Şöyle yaparız.
class Y{
public:
    Y(){std::cout << "Default constructor\n";}
    Y(const Y&) {std::cout << "Copy constructor\n";}
};

struct hasY {
    hasY() = default;
    hasY(hasY&&) = default;
    Y mem; 
};

int main(){
    hasY hy;
    hasY h=std::move(hy);
}
Çıktı olarak şunu alırız.
Default constructor
Copy constructor

4. Madde User Defined Destructor Varsa
Destructor yazmamızın sebebi "resource managing" bir sınıf olmamız. Resource yönetiyorsak zaten "move constructor", "move assignment oeprator" ve "destructor" metodlarını yazmamız gerekiyor.

Tüm bunları yazmak yerine resource, std::unique_ptr ile ile sarmalanabilir. Bu durumda bu 3 metodu yazmamız gerekmez yani, "Rule of Zero" kuralını uygulayabiliriz.

Örnek
Sınıfın destructor'ı varsa üretilmez.
class Widget {
public:
  ~Widget();         // no move move functions 
  ...                
};
Örnek
Elimizde move edilemeyen bir sınıf olsun.
B b;
B b2 = std::move(b);
Şu hatayı alırız.
invalid initialization of non-const reference of type 'B&' from an rvalue of type 'std::remove_reference<B&>::type {aka B}'
Örnek
Şu kod derlenmez.
struct A{
  std::unique_ptr<B> x;
  virtual ~A() = default;
};

A f() {
  A tmp;
  return tmp;
}
Hata olarak şunu alırız.
error: use of deleted function 'A::A(const A&)'
 return tmp;
        ^~~
Açıklaması şöyle.
virtual ~A() = default; is a user declared destructor. Because of that, A no longer has a move constructor. That means return tmp; can't move tmp and since tmp is not copyable, you get a compiler error.
Düzeltmek için şöyle yaparız.
struct A{
  std::unique_ptr<B> x;

  A() = default; // you have to add this since the move constructor was added
  A(A&&) = default; // defaulted move
  virtual ~A() = default;
};
Default Move Constructor
Şöyle yaparız.
B(B&&) = default;
Bizim için noexcept() imzalı kod üretilir.
B(B&&) noexcept {...};
Deleted Move Constructor
Move constructor deleted olarak işaretlense bile overload resolution için seçilmeyeceği anlamına gelmez, halen seçilebilir. Aynen header dosyasında imzası olan ancak cpp dosyasında kendisi olmayan metod gibidir.
Örnek
Şöyle yaparız.
class Boo
{
public:
  Boo(){}
  Boo(const Boo& boo) {};
  Boo(Boo&& boo) = delete;
};

Boo TakeBoo()
{
  Boo b;
  return b;
}
Şöyle bir hata alırız.
error C2280: 'Boo::Boo(Boo &&)': attempting to reference a deleted function
Örnek
Şöyle yaparız
#include <utility>

struct X
{
  X() = default;
  X(const X&) = default;
  X(X&&) = delete;
};

int main()
{
  X a;
  X b(std::move(a));
}
Çıktı olarak şunu alırız.
'X::X(X &&)': attempting to reference a deleted function

Hiç yorum yok:

Yorum Gönder