Menu
Coddy logo textTech

Mükemmel Yönlendirme

Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 84 / 104.

Argümanları kabul eden ve onları diğer fonksiyonlara ileten şablon fonksiyonları yazarken bir sorun ortaya çıkar: Orijinal argümanın bir lvalue mu yoksa rvalue mu olduğunu nasıl korursunuz? Perfect forwarding, argümanları tam olarak alındıkları gibi ileterek ve değer kategorilerini koruyarak bu sorunu çözer.

Temel bileşenler, yönlendirme referansları (bir şablon bağlamında T&& şeklinde yazılır) ve std::forward'dır. Bir yönlendirme referansı hem lvalue'lara hem de rvalue'lara bağlanabilir ve std::forward argümanı koşullu olarak orijinal türüne geri dönüştürür:

#include <iostream>
#include <utility>

void process(int& x) { std::cout << "lvalue: " << x << "\n"; }
void process(int&& x) { std::cout << "rvalue: " << x << "\n"; }

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

int main() {
    int n = 10;
    wrapper(n);       // process(int&) çağrılır - lvalue korunur
    wrapper(20);      // process(int&&) çağrılır - rvalue korunur
}

std::forward olmadan, isimlendirilmiş parametreler, bir rvalue geçilmiş olsa bile fonksiyon içinde her zaman lvalue'dur. Bu, kuruculara veya diğer fonksiyonlara iletim yaparken taşıma semantiğinin (move semantics) doğru çalışmasını engeller.

Mükemmel iletim (perfect forwarding); nesneler oluşturması veya fonksiyonları çağırması gereken fabrika fonksiyonları, sarmalayıcı sınıflar ve her türlü jenerik kodu yazarken, çağıranın taşıma (move) veya kopyalamaya (copy) yönelik niyetini korumak için gereklidir.

challenge icon

Görev

Kolay

Kendisine iletilen argümanların değer kategorisini (value category) koruyarak nesneler oluşturan ve mükemmel gönderimi (perfect forwarding) gösteren bir fabrika fonksiyonu inşa edelim. Argümanları bir sınıf kurucusuna ileten, lvalue'ların kopyalanmasını ve rvalue'ların taşınmasını sağlayan genel bir make_object fonksiyonu oluşturacaksınız.

Kodunuzu üç dosya halinde düzenleyeceksiniz:

  • Widget.h: Verilerini nasıl aldığını takip eden bir Widget sınıfı tanımlayın.

    Widget sınıfınız bir std::string name ve bir int value saklamalıdır. İki adet kurucu (constructor) sağlayın:

    • const std::string& (lvalue referansı) ve int alan — "Widget constructed (copy): [name]" yazdırır
    • std::string&& (rvalue referansı) ve int alan — "Widget constructed (move): [name]" yazdırır ve dizeyi taşır (move)

    Ayrıca [name]: [value] yazdıran bir display() metodu ekleyin.

  • Factory.h: Mükemmel gönderim yapan fabrika fonksiyonunuzu oluşturun.

    İsim için bir gönderim referansı (forwarding reference) ve değer için normal bir int kabul eden bir make_object şablon fonksiyonu yazın. İsim argümanını, orijinal değer kategorisini koruyarak Widget kurucusuna iletmek için std::forward kullanın.

    Fonksiyon, oluşturulan Widget nesnesini değer olarak döndürmelidir.

  • main.cpp: İki girdi okuyun:
    1. Bir isim (string)
    2. Bir değer (integer)

    İki widget oluşturarak mükemmel gönderimi gösterin:

    1. İlk olarak, ismi yerel bir değişkende saklayın ve make_object fonksiyonuna bir lvalue olarak geçirin
    2. İkinci olarak, geçici bir dizeyi (aynı ismin "_temp" ile birleştirilmiş hali) doğrudan make_object fonksiyonuna bir rvalue olarak geçirin

    Her widget'ı oluşturduktan sonra display() metodunu çağırın.

Örneğin, Gadget ve 42 girdileriyle:

Widget constructed (copy): Gadget
Gadget: 42
Widget constructed (move): Gadget_temp
Gadget_temp: 42

Device ve 100 girdileriyle:

Widget constructed (copy): Device
Device: 100
Widget constructed (move): Device_temp
Device_temp: 100

Buradaki temel nokta, make_object fonksiyonunuzun bir lvalue mu yoksa rvalue mu aldığını bilmemesidir; ancak std::forward kullanarak, Widget kurucusunu çağırırken bu bilgiyi korur. Bu, kurucu aşırı yükleme çözümlemesinin (overload resolution) uygun sürümü seçmesine olanak tanıyarak, isimlendirilmiş değişkenleri güvenli bir şekilde kopyalarken geçici nesneler için verimli taşımalar (moves) sağlar.

Kopya kağıdı

Mükemmel yönlendirme (perfect forwarding), şablon fonksiyonlarının argümanları orijinal değer kategorilerini (lvalue veya rvalue) koruyarak diğer fonksiyonlara aktarmasına olanak tanır.

Bu mekanizma, std::forward ile birleştirilmiş yönlendirme referanslarını (şablon bağlamında T&&) kullanır:

#include <utility>

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

Bir yönlendirme referansı hem lvalue hem de rvalue değerlere bağlanabilir. std::forward<T>, argümanı koşullu olarak orijinal türüne geri dönüştürür.

std::forward olmadan, bir fonksiyon içindeki isimlendirilmiş parametreler, bir rvalue geçilmiş olsa bile her zaman lvalue olarak değerlendirilir. Bu durum, taşıma semantiğinin (move semantics) doğru çalışmasını engeller.

Değer kategorisi korumasını gösteren örnek:

void process(int& x) { std::cout << "lvalue: " << x << "\n"; }
void process(int&& x) { std::cout << "rvalue: " << x << "\n"; }

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

int n = 10;
wrapper(n);       // process(int&) çağrılır - lvalue korunur
wrapper(20);      // process(int&&) çağrılır - rvalue korunur

Mükemmel yönlendirme; fabrika fonksiyonları, sarmalayıcı sınıflar ve nesneler oluşturan veya fonksiyonları çağıran genel (generic) kodlar için, çağıranın taşıma veya kopyalama konusundaki niyetini korumak açısından esastır.

Kendin dene

#include <iostream>
#include <string>
#include "Widget.h"
#include "Factory.h"

using namespace std;

int main() {
    // Girdiyi oku
    string name;
    int value;
    cin >> name >> value;
    
    // TODO: name değişkenini bir lvalue olarak geçirerek ilk widget'ı oluşturun
    // İsmi yerel bir değişkende saklayın ve make_object fonksiyonuna geçirin
    
    
    // TODO: İlk widget üzerinde display() fonksiyonunu çağırın
    
    
    // TODO: Geçici bir string'i rvalue olarak geçirerek ikinci widget'ı oluşturun
    // (name + "_temp") ifadesini doğrudan make_object fonksiyonuna geçirin
    
    
    // TODO: İkinci widget üzerinde display() fonksiyonunu çağırın
    
    
    return 0;
}
quiz iconKendini test et

Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.

Nesne Yönelimli Programlama bölümündeki tüm dersler