C++11 SAĞ DEĞER REFERANSLARI (R-VALUE REFERENCES) – 2

Bir önceki bölümde vector<string> dizisinin eski yöntemlerle kopyalanmaya kalktığında nasıl bir maliyet oluşturacağına dikkat çekmiştik. Şimdi bu örnek üzerinden gidelim.

Eski tarz vector kopyalanması:


#include <iostream>
#include <typeinfo>
#include <vector>

using namespace std;


void copyVector(vector<string> &v1, vector<string> &v2)
{
    for (auto s : v1) {
        v2.push_back(s);
    }
}


int main(int argc, char** argv) 
{
    vector<string> v1;
    vector<string> v2;

    v1.push_back("islam");
    v1.push_back("cpp");
    v1.push_back("turkiye");
    
    
    copyVector(v1, v2);
    
    for (auto s : v2)
        cout << s << endl;
   
    return 0;
}

Burda dikkat ederseniz v2 vektörüne 3 string nesnesi oluşturularak ekleniyor. Yani string sınıfının ctorları vasıtasıyla bu değerlerle v2 sınıfında bu nesneler inşaa ediliyor. Aynı şekilde
v2.push_back(s);
yerine
v2.push_back(std::move(s)) kullanırsak copy yerine move edecek. Bu arada derleyicinizin zaten c++11 uyumlu ise otomatik olarak move edecektir. __GXX_EXPERIMENTAL_CXX0X__ değişkenini tanımladığı için kopyalama maliyetinden kurtulmuş olacaksınız.

Peki std::move() kullanılarak move edilen nesneleri nasıl yakalayacağız. Bunun içinde move ctor şöyleki:

class cVector
{

public:
	cVector(cVector &&r)
	{
		cout << "rvalue move ctor" << endl;
	}
	
	cVector& operator= (cVector &&r)
	{
		cout << "operator = rvalue" << endl;
		return *this;
	}
	
	cVector & operator= (cVector&r)
	{
		cout << "operator = lvalue" << endl;
		return *this;
	}
	
	cVector ()
	{
		cout << "Default ctor" << endl;

	}
};

int main()
{
	cVector cv;
	cVector cv2(move(cv));

	cv = cv2;
	cv = move(cv2);
}

2-Perfect Forwarding

Bu durum rvalue referansları fonksiyon şablonlarında kullanmak isteyince “template argument deduction” yani fonksiyon argümanı belirleme durumunda ortaya çıkıyor. Şöyle ki:

template<typename T>
void f(T&& t);

int main()
{
    X x;
    f(x);   // 1
    f(X()); // 2
}

Örnekte 1 durumunda f fonksiyonuna bir lvalue parametre olarak verilmiş dolayısıyla T türü X& olarak belirlenecektir. 2 durumunda ise f fonksiyonuna bir rvalue reference geçilmiş böylelikle T türü X olarak belirlenecektir. Peki f fonksiyonu içerisinde başka bir fonksiyon çağırılmak istense nasıl gelen nesnenin rvalue mu lvalue mu oldgunu bileceğiz ?

void g(X&& t); // A
void g(X& t);      // B

template<typename T>
void f(T&& t)
{
    g(std::forward<T>(t));
}

void h(X&& t)
{
    g(t);
}

int main()
{
    X x;
    f(x);   // 1
    f(X()); // 2
    h(x);   // 3
    h(X()); // 4
}

Örnekte std::forward(t) yardımıyla gelen tür bir rvalue ise; içerisinde static_cast(t) operatörünü görüldüğü gibi çağırarak A durumundaki g fonksiyonun çağırılmasını sağlar. Gelen nesne bir lvalue ise B durumundaki g fonksiyonu çağırılacaktır(std::forward gelen nesne bir lvalue ise sadece aldığı argümanı geri döndürür). İşte tam olarak gelen argümanın tespitine göre içeride uygun fonksiyonun çağırılmasına perfect forwarding denir.

Normal fonksiyonda (h) ise (3,4) durumunda uygun overload çağırılır. Fakat A satırındaki g nin rvalue overload fonksiyonu silinse dahi çalışır. Çünkü gelen nesne bir rvalue referansa bir lvalue referanstır. g fonksiyonunun içinde yapılanlara göre tehlikeli bir durum olabilir.