C++0x - Sağ Taraf Değerine Bağlanan Referanslar (Rvalue References)



Benzer belgeler
C++ Dersi: Nesne Tabanlı Programlama

BTEP243 Ders 3. class Yazım Kuralı:

NESNEYE YÖNELİK PROGRAMLAMA

TEMPLATES. Binnur Kurt Bilgisayar Mühendisliği Bölümü İstanbul Teknik Üniversitesi. C++ ile Nesneye Dayalı Programlama 1

C++ Dersi: Nesne Tabanlı Programlama

NESNEYE YÖNELİK PROGRAMLAMA THIS İŞARETÇİSİ, KOPYA YAPICI FONKSİYON, STATİK ELEMANLAR, ARKADAŞ SINIF VE FONKSİYONLAR,NESNE DİZİLERİ

C++11'in Bazı Yenilikleri ve D'deki Karşılıkları

Operatörlere Yeni İşlevler Yüklenmesi (Operator Overloading)

NESNEYE YÖNELİK PROGRAMLAMA

1 PROGRAMLAMAYA GİRİŞ

C++ Dersi: Nesne Tabanlı Programlama

C++ Dersi: Nesne Tabanlı Programlama

Kurucu Fonksiyonlar (Constructors)

Dizi nin Önemi. Telefon rehberindeki numaralar, haftanın günleri gibi v.b.

Uzaktan Eğitim Uygulama ve Araştırma Merkezi

C++ Giriş Ders 1 MSGSU Fizik Bölümü Ferhat ÖZOK Kullanılacak kaynak: Published by Juan Soulié

Nesne İşaretçileri. Binnur Kurt Bilgisayar Mühendisliği Bölümü İstanbul Teknik Üniversitesi. Sınıf Yapısı. Kalıtım Çok Şekillilik

10/17/2007 Nesneye Yonelik Programlama 3.1

NESNEYE YÖNELİK PROGRAMLAMA C++ a Giriş

NESNEYE YÖNELİK PROGRAMLAMA

C++ Dersi: Nesne Tabanlı Programlama

Sınıflar ve Yapılar Arasındaki Farklılıklar. Değer ve Referans Türde Olan Aktarımlar

Önemli noktalar. Paradigma Nesnelere Giriş Mesajlar / Ara bağlantılar Bilgi Gizleme (Information Hiding ) Sınıflar(Classes) Kalıtım/Inheritance

Statik veri üyeleri sınıf dosyası içerisinde, ancak sınıf bildirimi dışında başlatılmalıdır. Statik üye fonksiyonları

NESNEYE YÖNELİK PROGRAMLAMA SINIFLAR

YZM 2105 Nesneye Yönelik Programlama

#ifndef COMPLEX_H #define COMPLEX_H

C++ Dersi: Nesne Tabanlı Programlama

C++ Dersi: Nesne Tabanlı Programlama

Pointer Kavramı. Veri Yapıları

C++ Operatörler (Operators)

Göstericiler (Pointers)

Operatörlere İşlev Yükleme

C++ Dersi: Nesne Tabanlı Programlama 2. Baskı

Fall Object-Oriented Programming Laboratory 02 - Structures

Örnek1: #include <iostream> #include <string> using namespace std;

Nesne tabanlı programlama nesneleri kullanan programlamayı içerir. Bir nesne farklı olarak tanımlanabilen gerçek dünyadaki bir varlıktır.

Genel Programlama II

C++ ile Nesneye Dayalı Programlama

C, C++, C# ve Java'da enum Türleri ve Sabitleri Benzerlikler ve Farklılıklar

Diziler. Yrd.Doç.Dr.Bülent ÇOBANOĞLU

Programlama Dilleri 3

Temel Veri Yapıları. (Veri Yapıları 1. Bölüm)

Konular. Hafta 5 Veri Tipleri (Devam) BLG339 PROGRAMLAMA DİLLERİ KAVRAMI

String ve Karakter Dizileri. Yrd. Doç. Dr. Fehim KÖYLÜ Erciyes Üniversitesi Bilgisayar Mühendisliği Bölümü

Standard Template Library

BĠLGĠSAYAR PROGRAMLAMA II C++ Programlamaya GiriĢ Published by Juan Soulié

Pros and Cons of Pointers. Pointers. Avantajlar. Dezavantajlar

Pointers (İşaretçiler)

C++ Class larina baslangic. C++ Versus C Object-oriented Language C++ Structure dan Object Create etmek. Structure tanimlama.

BİL-142 Bilgisayar Programlama II

#ifndef FATURA_H #define FATURA_H

Programlama Dilleri III 1

Nesne Yönelimli Programlama

Object-Oriented Programming Lab 4. - Sıcaklık değeri, Kelvin biriminde saklansın. Varsayılan sıcaklık değeri K olsun.

Yrd. Doç. Dr. Caner ÖZCAN

BM102 BİLGİSAYAR PROGRAMLAMA II LABORATUVAR UYGULAMALARI. 3Hafta

KOCAELİ ÜNİVERSİTESİ MÜHENDİSLİK FAKÜLTESİ

Lambda İfadeleri (Lambda Expressions)

Inheritance. Inheritance (turetim)

BLM 112- Programlama Dilleri II. Hafta 5 İşaretçiler (Pointers)

Bölüm 11. Soyut veri tipleri ve kapsülleme kavramları ISBN

BLM-111 PROGRAMLAMA DİLLERİ I. Ders-2 Değişken Kavramı ve Temel Operatörler

Temel Bilgisayar Programlama Final Sınavı Çalışma Notları

Upgrading Internet Technology skills of Information and Communication Technologies (ICT) Professionals

BMÜ-111 Algoritma ve Programlama. Bölüm 5. Tek Boyutlu Diziler

DİZİLER-KATARLAR ALGORİTMA VE PROGRAMLAMA II

Uzaktan Eğitim Uygulama ve Araştırma Merkezi

ALGORİTMA VE PROGRAMLAMA II

C++ Hata Düzeneği Güvenliği

BMH-303 Nesneye Yönelik Programlama

NESNEYE YÖNELİK PROGRAMLAMA

YAPILAR BİRLİKLER SAYMA SABİTLERİ/KÜMELERİ. 3. Hafta

Fonksiyonlar. C++ ve NESNEYE DAYALI PROGRAMLAMA 51. /* Fonksiyon: kup Bir tamsayının küpünü hesaplar */ long int kup(int x) {

Object-Oriented Programming Laboratuvar 10

işlemler bittikten sonra dosyaların kapatılması uygun olacaktır. Bunun için, fclose(fin);

Operator Aşırı Yükleme (Operator OverLoading)

BİL-142 Bilgisayar Programlama II

Nesne Tabanlı Programlama

Operatörlerin Aşırı Yüklenmesi

11- FONKSİYONLAR (FUNCTIONS)

Karakter katarları ile ilgili fonksiyonlar içerir Yerel kayan noktalı sayılar tanımlanır

Temel Bilgisayar Bilimleri Ders Notu #4-2. kısım

Dizi nin Önemi. Telefon rehberindeki numaralar, haftanın günleri gibi v.b.

Veri Yapıları Lab Notları 1

Hafta 12 Karakter Tutan Diziler

BİLİNİRLİK ALANI ve ÖMÜR, KONTROL DEYİMLERİ

const objects & const member functions const objects ve const functions Data Member Initializer List Data Member Initializer List

Örnek: İki fonksiyondan oluşan bir program. Fonksiyon Tanımı

const objects & const member functions

5.HAFTA. Sınıf ve Nesne Kavramı, Metot Oluşturma, Kurucu Metot, this Deyimi

SP_RENAMEDB eski_isim, yeni_isim VEYA SP_RENAMEDB 'eski isim', 'yeni isim'

Yrd. Doç. Dr. Caner ÖZCAN

Veri Yapıları ve Algoritmalar dönem

Nesneye Yönelik Programlama (OOP) 7.Hafta

Sunum İçeriği. Programlamaya Giriş

NESNE YÖNELİMLİ PROGRAMLAMA HAFTA # 2

DERSİN WEB SİTESİ:

RSA ANAHTAR DAĞITIMI VE RSA İLE DİJİTAL İMZA OLUŞTURMA

HSancak Nesne Tabanlı Programlama I Ders Notları

Transkript:

C++0x - Sağ Taraf Değerine Bağlanan Referanslar (Rvalue References) Kaan Aslan 25 Ağustos 2008 C++ a eklenmesine karar verilen yeni bir özellik de sağ taraf değerine bağlanan referanslardır. C++0x standart taslaklarında normal refereranslara sol taraf değerine bağlanan referanslar (lvalue references) denilmektedir. Bu taslaklarda referans terimi hem sağ taraf değerine hem de sol taraf değerine bağlanan referansları kapsayacak biçimde kullanılmaktadır. Sağ taraf değerine bağlanan referanslar dekleratörde && atomu ile bildirilirler: int x; int &a = x; int &&b = foo(); /* normal referans (sol taraf değerine bağlanan referans) */ // sağ taraf değerine bağlanan referans Deklaratördeki && atomlarını ** atomlarına benzeterek referans referansları sanmayın. Bu tamamen başka bir anlama geliyor. Öncelikle normal bir referansa (sol taraf değerine bağlann referansa) verilen ilkdeğerin aynı türden nesne belirten bir ifade (yani sol taraf değeri) olması gerektiğini anımsatalım: int x; int &a = x; int &b = 10; int &c = foo(); Eğer referansa verilen ilkdeğer aynı türden bir nesne belirtmiyorsa referansın const olması gerekir. const olan normal referanslara referansın türüne dönüştürülebilen herahangi bir ifadeyle ilkdeğer verilebilir. Örneğin: double x; int &a = foo(); const int &b = foo(); int &c = 10; const int &c = 10; int &d = x; const int &d = x; 1

const olan normal bir referansa kendi türünden nesne belirtmeyen bir ifadeyle ilkdeğer verildiğinde derleyici tarafından geçici bir nesne yaratılır. Verilen ilkdeğer geçici nesneye atanır ve o geçici nesnenin adresi referansa yerleştirilir. Örneğin: double d = 3.2; const int &r = d; İşlemiyle aslında şunlar yapılmaktadır: double d = 3.2; const int temp = d; const int &r = temp; Bu biçimde yaratılan geçici nesnenin ömrü referansın faaliyet alanı ile ilişkilidir. Sağ taraf değerine bağlann referansların (rvalue references) sol taraf değerine bağlanan referanslardan (lvalue references) en önemli farklılığı sağ taraf değerine bağlanan referanslara sağ taraf değeri ile ilkdeğer verilebilmesidir. Bu durumda sağ taraf değerine bağlanan referanslar const olmak zorunda değildir. Yani sol taraf değerine bağlanan referanslara verilen ilkdeğerlerin nesne belirten ifadeler olması gerekirken sağ taraf değeri alan referanslara verilen ilkdeğerler için böyle bir zorunluluk yoktur. Örneğin: int &a = foo(); int &&b = foo(); Sağ taraf değerine bağlanan referanslar sol taraf değerleriyle (nesne belirten ifadelerle) de sağ taraf değeriyle de ilkdeğer verilerek bildirilebilir. Yani sağ taraf değerine bağlanan referanslar kullanım bakımından sol taraf değerine bağlanan referansları kapsamaktadır. Örneğin: int x; int &&a = x; int &&b = foo(); Sağ taraf değerine bağlanan referanslara verilen ilkdeğerler eğer sol taraf değeri ise doğrudan ilgili nesnenin adresi referansa yerleştirilir değilse yine geçici nesne yaratılarak geçici nesnenin adresi referansa yerleştirilir. Ancak yukarıda da belirttiğimiz gibi bu durumda sağ taraf değerine bağlanan referanslar const olmak zorunda değildir ve bu referanslarla geçici nesneler değiştirilebilir. Örneğin: double d = 3.2; int &&r = d; r = 5; // geçici nesne değiştiriliyor 2

Sol taraf değerine bağlanan referanslarla sağ taraf değerine bağlanan referanslar farklı türler belirttiklerinden fonksiyonların imzalarını (signatures) değiştirirler. Bu nedenle aynı isimli biri sol taraf değerine bağlanan diğeri sağ taraf değerine bağlanan fonksiyonlar aynı faaliyet alanında birarada bulunabilir. Örneğin: void foo(int &r); void foo(int &&r); Overload resolution işlemi için standart taslaklarına otomatik tür dönüştürmelerinde (implicit type conversions) derece belirlemeye (ranking) yönelik ek bir madde eklenmiştir (13.3.3-3). Buna göre: e ifadesi T & ve T && türlerine doğrudan dönüştürülebiliyor olsun. Eğer e ifadesi sol taraf değeri belirtiyorsa T & dönüştürmesi, sağ taraf değeri belirtiyorsa T && dönüştürmesi daha iyidir. Örneğin: void foo(const int &r); // 1 void foo(const int &&r); // 2 foo(100); Burada her iki fonksiyon da aday fonksiyondur. 100 sağ taraf değeri olduğu için ikinci fonksiyonun dönüştürme derecesi birinciden daha iyidir. Overload resolution işlemi sonucunda ikinci fonksiyon seçilir. Şimdi de şu örneğe bakınız: void foo(const int &r); // 1 void foo(const int &&r); // 2 int a; foo(a); Burada birinci fonksiyon daha iyi dönüştürme derecesine sahiptir. Overload resolution işlemi sonucunda birinci fonksiyon seçilir. Aşağıdaki örneğe bakınız: void foo(int &r); // 1 void foo(int &&r); // 2 foo(100); Burada zaten birinci fonksiyon aday fonksiyon (candidate function) değildir. Dolayısıyla overload resolution işlemine yalnızca ikinci fonksiyon girer. Sağ taraf değeri belirten bazı ifadeleri anımsatalım: - Sabitler. - Geri dönüş değeri referans olmayan fonksiyon çağrıları. Örneğin: int &&bar(); int &tar(); 3

foo() ve bar() sağ taraf değeri belirtir. Fakat tar() ifadesi sol taraf değeri belirtir. (Sağ taraf değeri alan referansların sol taraf değeri belirttiğine fakat fonksiyonun geri dönüş değeri sağ taraf değeri alan referanssa fonksiyon çağırma ifadesinin sağ taraf değeri belirttiğine dikkat ediniz.) - Geçici nesneler. Örneğin: class X ; X() ifadesi sağ taraf değeridir. - Çeşitli operatörlerle oluşturulan ifadeler. Örneğin: int a, b; a + b ifadesi sağ taraf değeri belirtir. Sınıfların static olmayan üye fonksiyonlarına geçirilen this parametreleri overload resolution işleminde referans olarak ele alınmaktadır. Bu referans fonksiyonun ilk parametresini oluşturur. Bu parametreye gizli nesne parametresi (implicit object parameter) diyeceğiz. Bu durumda fonksiyonun çağrıldığı nesne de bu parametreye karşı gelen argümandır. Bu argümana da gizli nesne argümanı (implicit object argument) diyeceğiz. C++0x te üye fonksiyonların gizli nesne parametreleri sağ taraf değeri alan referans ya da sol taraf değeri alan referans yapılabilir. Örneğin: class Sample public: void foo() &; void foo() &&; ; Gördüğünüz gibi bu belirlemeyi yapmak için deklaratörde fonksiyonun parametre parantezinden sonra & ya da && atomları yerleştirilir. & ve && belirleyicileri fonksiyonun imzasını değiştirmektedir. Bu belirleyicilerin tanımlama sırasında da kullanılması gerektiğini belirtelim. Böylece bu iki fonksiyon overload resolution işleminde farklı etkilere yol açar. Örneğin: Sample s; Sample().foo(); s.foo(); // foo() && çağrılır // foo() & çağrılır & belirleyicisini belirtmekle belirtmemek aynı anlamdadır. Yani default durum zaten gizli nesne parametresinin sol taraf değeri alan referans olmasıdır. Burada dikkat edilmesi gereken nokta şudur: Sol taraf değeri alan gizli nesne parametresine sahip üye fonksiyon sağ taraf değeri belirten nesneyle çağrılabilir. Yani: 4

class Sample public: void foo() &; ; Sample().foo(); Gizli nesne parametresinin sol taraf değeri ya da sağ taraf değeri belirtmesi sınıfta hem & hem de && belirleyicili aynı isimli fonksiyon bulunduğu zaman farklılık oluşmaktadır. Peki sağ taraf değeri alan referanslara neden gereksinim duyulmuştur? Yanıt oldukça basit: Taşıma düzeneğini (move semantics) sağlamak için. Aşağıdaki örneği inceleyiniz: std::vector<int> func() std::vector<int> a; // elemanlar ekleniyor return a; std::vector<int> b; b = func(); /* kopya başlangıç fonksiyonu ve kopya atama operatör fonksiyonu çağrılacak */ Burada b = func() işlemiyle önce fonksiyonun geri dönüş değeri için geçici nesne yaratılacak ve bu nesne için kopya başlangıç fonksiyonu (copy constructor) çağrılacak, daha sonra b ataması için bu kez kopya atama operatör fonksiyonu çağrılacaktır. Halbuki return işlemi ile yaratılacak geçici vector<int> nesnesi doğrudan yerel nesne tarafından tahsis edilmiş olan alanı kullanabilirdi ve benzer biçimde b ye atama işleminde yine taşıma yapılarak bu alan b ye aktarılabilirdi. Taşıma düzeneğini oluşturabilmek için X gibi bir sınıfa X && parametreli bir başlangıç fonksiyonunun ve atama operatör fonksiyonunun yerleştirilmesi gerekir. Sınıfın X && parametreli başlangıç fonksiyonuna taşıma başlangıç fonksiyonu (move constructor), X && parametreli operatör fonksiyonuna taşıma operatör fonksiyonu (move assignment operator) denilmektedir. Taşıma düzeneği için düşünülmüş olan move isimli fonksiyon şablonu da önemli bir işleve sahiptir: template <class T> T &&move(t &&a) return a; 5

Bu fonksiyonun yaptığı tek şey argüman olarak aldığı ifadeyi sağ taraf değeri olarak vermektir. Böylece elimizde bir sol taraf değeri varsa biz onu hiç değiştirmeden sağ taraf değerine dönüştürebiliriz. Örneğin: template <class T> void swap(t &a, T &b) T tmp(std::move(a)); a = std::move(b); b = std::move(tmp); Öncelikle taşıma mekanizmasının anlaşılması gerekiyor. Bunun için basit bir örnek vermek istiyoruz. Örneğin anlaşılabilir olması için özellikle şablon kullanmamayı tercih ediyoruz: #include <iostream> #include <cstdlib> #include <algorithm> #include <utility> using namespace std; class IntArray public: IntArray() : m_parray(0), m_size(0) IntArray(std::size_t size) : m_parray(new int[size]), m_size(size) ~IntArray() delete [] m_parray; // Kopya başlangıç fonksiyonu IntArray(const IntArray &r) m_parray = new int[r.m_size]; std::copy(r.m_parray, r.m_parray + r.m_size, m_parray); // Taşıma başlangıç fonksiyonu IntArray(IntArray &&r) m_parray = r.m_parray; r.m_parray = 0; 6

// Kopya atama operatör fonksiyonu IntArray &operator =(const IntArray &r) if (&r!= this) delete [] m_parray; m_parray = new int[r.m_size]; std::copy(r.m_parray, r.m_parray + r.m_size, m_parray); return *this; // Taşıma atama operatör fonksiyonu IntArray &operator =(IntArray &&r) std::swap(m_parray, r.m_parray); std::swap(m_size, r.m_size); return *this; int &operator [](std::size_t index) return m_parray[index]; int size() const return m_size; private: int *m_parray; std::size_t m_size; ; Örneğimizde int türden bir diziyi temsil eden IntArray isimli bir sınıf görüyorsunuz. Sınıfın kopya başlangıç fonksiyonu int türden dinamik bir dizi tahsis ederek parametresiyle aldığı sınıfın dizi elemanlarını bu alana kopyalamaktadır. IntArray(const IntArray &r) m_parray = new int[r.m_size]; std::copy(r.m_parray, r.m_parray + r.m_size, m_parray); IntArray nesnesini IntArray türünden bir sol taraf değeri ile ilkdeğer vererek yaratırsak bu başlangıç fonksiyonu çalıştırılacaktır. Şimdi taşıma başlangıç fonksiyonuna bakınız: 7

IntArray(IntArray &&r) m_parray = r.m_parray; r.m_parray = 0; taşıma işleminden sonra ilkdeğer verilen nesnenin m_parray elemanına NULL adres sabitinin atandığına dikkat ediniz. Şimdi taşıma başlangıç fonksiyonunu kullanalım: IntArray a(10); for (int i = 0; i < 10; ++i) a[i] = i; Bu işlemlerden sonra a nesnesi şöyle olacaktır: IntArray b = a; Şimdi olanları inceleyelim: Artık a nesnesinin m_parray elemanında NULL adres var. (NULL adrese delete işlemi uygulamanın bir sakıncası olmadığını anımsayınız.) Gördüğünüz gibi taşıma işlemi gerçekleştirilmiş, gereksiz bir biçimde tahsisat ve kopyalama yapılmamıştır. Şimdi atama operatör fonksiyonuna bakınız: 8

IntArray &operator =(const IntArray &r) if (&r!= this) delete [] m_parray; m_parray = new int[r.m_size]; std::copy(r.m_parray, r.m_parray + r.m_size, m_parray); Burada klasik işlemlerin yapıldığını görüyorsunuz. Önce eski dizi serbest bırakılmış, sonra yeni dizi için yer tahsis edilmiş ve yeni diziye kopyalama yapılmıştır. Şimdi de taşıma operatör fonksiyonunu inceleyiniz: IntArray &operator =(IntArray &&r) std::swap(m_parray, r.m_parray); std::swap(m_size, r.m_size); return *this; Burada ise std::swap fonksiyonuyla m_parray ve m_size değerlerinin yer değiştirildiğini görüyorsunuz. Bunun anlamı nedir? Aşağıdaki örnekle açıklayalım: IntArray foo() IntArray a(10); for (int i = 0; i < 10; ++i) a[i] = i; return a; int main(int argc, char** argv) IntArray b(5); for (int i = 0; i < 10; ++i) b[i] = -i; b = foo(); for (int i = 0; i < b.size(); ++i) cout << b[i] << endl; return 0; Burada b = foo() işlemine odaklanınız. Bu işlemle taşıma operator fonksiyonu çağrılacak değil mi? foo fonksiyonunun geri döndürdüğü nesneye temp diyelim. Önce main fonksiyonunda a nesnesi yaratıldığında şöyle bir durum oluşacaktır: 9

Şimdi foo fonksiyonunun çağrılmasıyla yaratılan geçici nesneyi betimleyelim: Şimdi de b = foo() işlemi sonucunda olanlara bakalım: Burada veri elemanlarının karşılıklı yer değiştirmesi ile b nin m_parray elemanı temp in m_parray elemanı haline gelmiştir. temp için çağrılacak bitiş fonksiyonu b nin gösterdiği eski alanı serbest bırakacaktır. Sağ taraf değerine bağlanan referaslar kopyalanamayan bazı türlerin taşınmasını (movable but not copyable types) mümkün hale de getirebilmektedir. Örneğin bir fstream nesnesinin kopyalanması anlamlı değildir fakat taşınması anlamlı olabilir. 10

Kaynaklar Hinnant, H. (2006). A Proposal to Add an Rvalue Reference to the C++ Language - Revision 3. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2118. html adresinden alınmıştır. Hinnant, H., Abrahams, D., & Dimov, P. (2004). A Proposal to Add an Rvalue Reference to the C++ Language. http://www.open-std.org/jtc1/sc22/wg21/docs/ papers/2004/n1690.htm Hinnant, H., Stroustrup, B., & Kozicki, B. (2008). A Brief Introduction to RValue References. http://www.artima.com/cppsource/rvalue.html adresinden alınmıştır. Working Draft, Standard for Programming Language C++ (N2798=08-0308). (2008). http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/ adresinden alınmıştır. 11