C++ lvalue ve rvalue

Umut Dağ
3 min readDec 1, 2020

lvalue ve rvalue terimleri, dili geliştirirken çok sık karşılaşılan terimler olmayan ve karşılaşıldığında ise anlaşılması basit olmayan terimlerdir. Başlangıç seviyesinde, bu terimlere rastlanan yerler çoğunlukla derleyici hatalarıdır. Aşağıdaki örnek kodu g++ ile derlediğimiz zaman:

int foo() {return 2;}

int main()
{
foo() = 2;

return 0;
}

foo() = 2; satırında eşitlikteki sol işlenenin lvalue olması gerektiği hata mesajını alırız. Böyle bir kodun yazılmayacağını herkes bilir ancak önemli olan nokta hata mesajının çok sık karşılaşılmayan lvalue ifadesini içermesi. Şimdi aşağıdaki bir diğer örnek kodu g++ ile derlediğimiz zaman:

int& foo()
{
return 2;
}

“Sabit olmayan ‘int&’ tipindeki referansın ‘int’ tipindeki rvalue kullanılarak geçersiz olarak başlatılması” hata mesajını alırız.

Nedir bu lvalue ve rvaluelar? lvalue (yer belirleğici değeri) hafızada belirli bir yeri gösterir. Diğer bir yandan rvalue, lvalue’nun birebir zıttı olarak tanımlanmış olup, hafızada herhangi bir yer göstermezler. Böylelikle bir ifade, ya sadece lvalue ya da sadece rvalue olabilir.

Birkaç örneğe bakarak tanımları daha anlaşılabilir yapabiliriz. Aşağıdaki kodu incelediğimizde:

int var;
var = 4; // Hata yok
4 = var; // Hata var !
(var + 1) = 4; // Hata var !

Atama işlemi sol işleneni olarak lvalue gerektirdiği için ve var bir lvalue olduğu için 2. satırda hiçbir hata yok, ancak sonrasında gelen diğer satırlarda, eşitliğin sol tarafında bulunan 4 ve var + 1 lvalue değiller (tanımımıza göre, lvalue olmayan ifadeler rvalue idi, bu da iki ifadeyi de rvalue yapar). İki ifadenin de lvalue olmamasının sebebi, iki ifade de geçiçi bir işlemin/ifadenin sonucu olduğu için hafızada spesifik bir lokasyon göstermezler. Böylelikle hafızada olmayan bir yere atama yapmaya çalışmak hataya sebep olur.

İlk kod parçacığında bulunan hata mesajının ne anlama geldiği şu an daha anlaşılabilir olmalı. foo fonksiyonu geçici bir değer, rvalue, dönüyor. Bu değere bir şey atamaya çalışmak ise hataya sebep oluyor. Yani foo() = 2; gördüğü zaman derleyici, eşitliğin sol tarafında lvalue bekliyor ancak rvalue gördüğü için şikayet ediyor.

Ancak her fonksiyonların döndüğü değerlere yapılan atamalar hataya sebep olmuyor. Örneğin, C++’da referanslar böyle bir atamayı mümkün kılabiliyor:

int globalvar = 20;

int& foo()
{
return globalvar;
}

int main()
{
foo() = 10;
return 0;
}

Yukarıdaki kod parçacığında, foo fonksiyonu bir referans, yani lvalue, dönüyor. Bu sebeple foo fonksiyonundan dönülen değere bir değer atanılabilir.

Aşağıdaki kod parçacığına bakalım:

int a = 1;
int b = 2;
int c = a + b;

Yukarıdaki kod parçacığında a ve b lvalue ancak + operatörü için rvalue gerekli. Toplama işleminin yapılması için a ve b üstü kapalı olarak rvalue’ya çeviriliyor. Dizi, fonksiyon veya tamamlanmamış tip türünden olmayan lvalue’ların tamamı üstü kapalı şekilde rvalue’ya çevirilebilir.

Üstü kapalı olarak bir rvalue’nun lvalue’ya çevirilmesi mümkün değildir ancak bu bilinçli olarak çevirilmeyeceği anlamına gelmez. Aşağıdaki kod parçacığını inceleyelim:

int arr[] = {1, 2};
int* p = &arr[0];
*(p + 1) = 10; // Hata yok: p + 1 rvalue ancak *(p + 1) lvalue

Yukarıdaki kod parçacığında * operatörü rvalue alıyor ve lvalue’ya çeviriyor. Bu operatörün aksine, & operatörü ise lvalue alıyor ve rvalue’ya çeviriyor. Aşağıdaki kod parçacığını inceleyelim:

int var = 10;
int* bad_addr = &(var + 1); // Hata: & için lvalue gerekli
int* addr = &var; // Hata yok: var bir lvalue
&var = 40; // Hata: eşitliğin sol tarafı için
// lvalue gerekli

C++’da & operatörünün bir diğer rolü ise referans tipleri tanımlamamıza olanak sağlaması. Bu referans tiplerine ise lvalue referansları denir. Sabit olmayan lvalue referanslarına rvalue ataması yapılamaz. Ancak sabit olan lvalue referanslarına rvalue ataması yapılmak istendiğinde bu mümkündür çünkü sabitler referans aracılığıyla değiştirilemezler, bu da rvalue ifadesinin değiştirilmesi problemini ortadan kaldırır.

--

--