← Back to list

Modern C++: Değer Kategorileriyle std::move’un İç Yüzü

Modern C++ öğrenmeye başlayanların anlamakta zorluk çektiği bir konuyu anlatmaya çalışacağım. Öncelikle, std::move fonksiyonunu anlamak…

Ömer Faruk Kalkan · 2025-05-30 18:23 · 0 claps · 3.0 min read
#cpp #value-categories #move-semantics #c-plus-plus-language
Open on Medium ↗
Wiki topics: LNG · Linguistics & Language

Modern C++: Değer Kategorileriyle std::move’un İç Yüzü

Modern C++ öğrenmeye başlayanların anlamakta zorluk çektiği bir konuyu anlatmaya çalışacağım. Öncelikle, std::move fonksiyonunu anlamak için değer kategorilerini bilmek gerekiyor. Peki, bu değer kategorileri nelerdir, cevabını verelim.

C++11 versiyonundan itibaren Temel(Primacy) değer kategorileri şunlardır:

  • lvalue
  • xvalue
  • prvalue

Karışık(Mixed) değer kategorileri de şunlardır:

  • glvalue
  • rvalue

Yukarıdaki kelimeleri kod yazarken kullanmıyoruz. Ancak bunlar, derleyici tarafından ifadeler çevrilirken kullanılmakta. Biz de bunları bilmeliyiz ki std::move kullanırken derleyiciyle uyum içinde olup hatalı işlemler yapmayalım.

Başlangıçta değer kategorilerini incelemek için elimizde aşağıdaki gibi bir atama ifadesi olsa yeterlidir:

int sayi = 0;

Bu ifadeyi yazarken kullandığımız kelimeler:

  • bir adet tür
  • bir adet isim
  • bir adet atama operatörlerinden birisi
  • bir adet değer

şeklindedir. Peki, değer kategorisi bu işin neresinde?

Yukarıda yazdığımız atama ifadesi aslında değer kategorilerini dolaylı olarak belirtmektedir. Eğer kategorisini incelediğiniz bir kelimenin sayi gibi bir adı varsa, bu ifadenin değer kategorisi lvalue olarak bilinir. lvalue kategorisindeki değerlere adlarıyla erişebiliriz.

Basitçe bu ifadenin içinde:

  • Bellekte bir adresi temsil eden ve ismi olan ifadeler lvalue (locator value)
  • lvalue olmayan değerler ise rvalue (right value)

kategorilerinde yer almaktaydı. Tabii, eskiden böyleydi ve bu kadar kolaydı demek doğru olacaktır… std::move icat edilmeden önce.

Artık işlerin zorlaştığını hissettiyseniz değişken isimlerine bakmak yetmez, expression incelememiz gerekecek. Çünkü C++11'den itibaren değer kategorileri expression sonucuna göre değerlendiriliyor.

C++’ta değer döndüren veya döndürmeyen ifadeler yazabiliyoruz. Bir ifade değerlendirildiğinde bir değer oluşturabilir veya oluşturmayabilir de. İşte tüm mesele bu oluşan değerlere sayi gibi bir isimle tekrar erişebiliyor muyuz ve bu oluşan değeri std::move ile taşıyabiliyor muyuz üzerinedir.

Bundan sonrasını daha kolay öğrenmek için aşğıdaki venn diyagramına ve tabloya bakalım.

C++ Değer Kategorileri

C++ Değer Kategorileri

Tabloyu ve venn diyagramını bir ifadeyi incelemek için şöyle kullanıyoruz;

Eğer bir ifadeden elde edilen değer adlandırılabiliyorsa ve bellekte bir adresle ilişkilendirilebiliyorsa, bu değer bir glvalue’dır. glvalue terimi, generalized lvalue ifadesinin kısaltmasıdır ve lvalue ya da xvalue olabilir. Bu iki alt kategoriden hangisine girdiğini belirlemek için ifadenin kullanım şekline ve bağlamına bakmamız gerekir.

Genel bir yanlış kanının aksine, bir ifadenin atama operatörünün sol ya da sağ tarafında yer alması, doğrudan değer kategorisini belirlemez. Örneğin, sayi gibi bir değişken her zaman lvalue’dur, ister sol tarafta ister sağ tarafta bulunsun. Ancak bir lvalue, std::move ile açıkça taşınabilir hale getirilirse, bu durumda xvalue kategorisine girer. Bu dönüşüm derleyiciye, taşımaya (move) uygun yapıcı veya atama fonksiyonlarının kullanılabileceğini ifade eder.

Bu yüzden, bir ifadenin değer kategorisini anlamak için sadece nerede kullanıldığına değil, aynı zamanda onun isimli mi, isimsiz mi, taşınabilir mi, taşınamaz mı, ve std::move ile işaretlenmiş mi olduğuna dikkat etmek gerekir. Çünkü bir lvalue std::move ifadesi ile kullanılmazsa sadece kopyalama işlemi gerçekleşebilir.

Taşınamayan ama kopyalanabilen lvalue değerler, kopyalama işlemi sırasında hala lvalue kategorisinde değerlendirilir. Burada sık karşılaşılan kafa karışıklığı, “taşınamaz olarak işaretlenmiş bir lvalue değeri atamanın sağ tarafında nasıl kullanabiliyoruz?” sorusudur. Bunun nedeni, derleyicinin lvalue’yu okuyarak hedef nesneye kopyalamasıdır.

Taşınamaz ve kopyalanamaz bir lvalue ise atama operatörüyle kullanıldığında derleme hatası alınacaktır.

prvalue kategorisi ise anlaması en kolay olanlardan biridir. pure-value kısaltmasıdır. Bu kategorideki değerler bazen sadece 5 gibi bir sayı bazen true bazen de nullptr olabilir. Ya da aşağıdaki gibi birden fazla değerlerin sonucu da olabilir.

Gelelim rvalue kategorisine. right value kısaltmasıdır.

Örneğin:

(5 + 5 * sayi)

Eğer elimizdeki değer yukarıdaki gibi isimsiz bir expression ile oluşmuşsa 1. adımda bu expression sonucunda oluşan değer rvalue kategorisindedir. 2. adımda bu değerin taşınabilirliğine bakmamız gerekiyor. Yukarıdaki gibi isimsiz bir ifadenin sonucu prvalue kategorisindedir. Bu tür değerler genellikle bellekte fiziksel bir nesne oluşturmaz. Çoğu zaman derleyici, bu değeri doğrudan hedef nesneye yerleştirerek kopya ihmali (copy elision) optimizasyonunu yapar. C++17 ile birlikte, bazı durumlarda bu optimizasyon zorunlu hale gelmiştir.

Dolayısıyla, eğer bir ifade sonucunda isimsiz, geçici bir değer oluşuyorsa, bu bir prvalue’dur. rvalue genel bir kategori olup, prvalue’lar ve xvalue’lar rvalue kapsamına girer.

Expression sonucu oluşan değerlerin kategorilerini anladıktan sonra xvalue kavramını daha detaylı incelemek gerekir. xvalue, eXpiring value yani "ömür süresi sona ermek üzere olan değer" anlamına gelir. Genellikle std::move ile işaretlenmiş bir nesne bu kategoriye girer. Bu tür değerlere sonradan erişmeye çalışmak hatalı sonuçlar doğurabilir, çünkü taşınan nesnenin durumu artık tanımsız olabilir. std::move sonrası bir nesne hala var olabilir ama geçerli bir içeriğe sahip olmayabilir. Bu yüzden taşınmış nesneleri tekrar kullanmak, xvalue ile ilgili yapılan en yaygın hatalardan biridir.

Ve sonuç olarak asıl konunun kahramanına, std::move fonksiyonuna gelelim. Aslında std::move, adından farklı olarak herhangi bir veri taşıma işlemi yapmaz. Bu fonksiyon yalnızca bir nesneyi "taşınabilir" xvalue hale getirerek derleyiciye uygun move constructor veya move assignment fonksiyonlarının çağrılabileceğini belirtir. Teknik olarak std::move, sadece bir static_cast işlemi yapar. Tanımına baktığımızda bunu açıkça görebiliriz:

template <class _Ty>
constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept {
  return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}

메타데이터
post_id
270c0ae5bcee
slug
modern-c-değer-kategorileriyle-std-moveun-i̇ç-yüzü-270c0ae5bcee
url
https://medium.com/@omerfarukkalkan2016/modern-c-de%C4%9Fer-kategorileriyle-std-moveun-i%CC%87%C3%A7-y%C3%BCz%C3%BC-270c0ae5bcee
canonical_url
https://medium.com/@omerfarukkalkan2016/modern-c-de%C4%9Fer-kategorileriyle-std-moveun-i%CC%87%C3%A7-y%C3%BCz%C3%BC-270c0ae5bcee
author_url
https://medium.com/@omerfarukkalkan2016
status
ok
fetched_at
2026-07-21 11:03:45