Menu
Coddy logo textTech

Pimpl İdiyomu

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

Pimpl deyimi (Pointer to Implementation), bir sınıfın uygulama detaylarını ayrı, önceden bildirilmiş bir sınıfa taşıyarak gizleyen bir tekniktir. Bu, derleme bağımlılıklarını azaltır ve özel üyelerin başlık dosyasından gerçekten gizli kalmasını sağlar.

Temel fikir basittir: özel üyeleri doğrudan sınıfınızda bildirmek yerine, yalnızca kaynak dosyasında tanımlanan bir uygulama sınıfına bir işaretçi bildirirsiniz:

// Widget.h
#include <memory>

class Widget {
public:
    Widget();
    ~Widget();
    void doSomething();
    
private:
    class Impl;  // Ön bildirim
    std::unique_ptr<Impl> pImpl;
};
// Widget.cpp
#include "Widget.h"
#include <iostream>

class Widget::Impl {
public:
    int data = 42;
    void process() { std::cout << "Processing: " << data << "\n"; }
};

Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;

void Widget::doSomething() { pImpl->process(); }

Temel avantajlar derleme güvenlik duvarı ve ikili uyumluluktur. Impl sınıfını değiştirdiğinizde, başlık dosyasını içeren her dosyanın değil, yalnızca kaynak dosyanın yeniden derlenmesi gerekir. Bu, büyük projelerde derleme sürelerini önemli ölçüde hızlandırır.

Yıkıcının (destructor) kaynak dosyasında tanımlanması gerektiğini (varsayılan olarak belirlenmiş olsa bile) unutmayın; çünkü unique_ptr, onu silmek için Impl'in tam türüne ihtiyaç duyar. Bu, Pimpl'ı ilk kez kullanırken karşılaşılan yaygın bir hatadır.

challenge icon

Görev

Kolay

Şifreleme uygulama ayrıntılarını başlık dosyasından gizlemek için Pimpl deyimini (idiom) kullanarak güvenli bir mesaj işleyici oluşturalım. Bu, Pimpl'ın nasıl bir derleme güvenlik duvarı (compilation firewall) oluşturduğunu gösterir; başlık dosyanızı dahil eden hiç kimse mesaj işleme sürecinizin dahili işleyişini göremez.

Kodunuzu üç dosya halinde düzenleyeceksiniz:

  • SecureMessage.h: SecureMessage sınıfınız için genel arayüzü tanımlayın.

    Sınıfınızda önceden bildirilmiş (forward-declared) bir Impl sınıfı ve ona işaret eden bir std::unique_ptr bulunmalıdır. Genel arayüz şunları içermelidir:

    • Orijinal mesaj için bir const std::string& alan bir yapıcı (constructor)
    • Bir yıkıcı (destructor) (burada bildirilmeli, .cpp dosyasında tanımlanmalıdır)
    • Bir şifreleme anahtarı ayarlamak için setKey(int key) metodu
    • Şifrelenmiş mesajı std::string olarak döndüren bir getEncrypted() metodu
    • Orijinal mesajı döndüren bir getOriginal() metodu

    Başlık dosyası yalnızca genel arayüzü göstermelidir; şifrelemenin nasıl çalıştığına dair hiçbir uygulama ayrıntısı burada görünmemelidir.

  • SecureMessage.cpp: İç içe geçmiş Impl sınıfını tanımlayın ve tüm metotları uygulayın.

    Impl sınıfınız orijinal mesajı, şifreleme anahtarını (varsayılan olarak 0) saklamalı ve asıl şifreleme mantığını yönetmelidir. Şifreleme için basit bir Sezar şifrelemesi kullanın: her karakteri anahtar değeri kadar kaydırın. Örneğin, 3 anahtarı ile 'a', 'd' olur; 'z' ise başa dönerek 'c' olur.

    Şifreleme yalnızca küçük harfleri (a-z) etkilemeli, diğer tüm karakterleri değiştirmeden bırakmalıdır. unique_ptr tam Impl türüne ihtiyaç duyduğundan, yıkıcıyı burada tanımlamayı (varsayılan olsa bile) unutmayın.

  • main.cpp: İki girdi okuyun:
    1. Bir mesaj dizesi (boşluk içerebilir)
    2. Bir şifreleme anahtarı (tam sayı)

    Bir SecureMessage nesnesi oluşturun, anahtarı ayarlayın ve sonuçları görüntüleyin:

    1. Original: ve ardından orijinal mesajı yazdırın
    2. Encrypted: ve ardından şifrelenmiş mesajı yazdırın

Örneğin, hello world ve 3 girdileriyle:

Original: hello world
Encrypted: khoor zruog

xyz abc ve 5 girdileriyle:

Original: xyz abc
Encrypted: cde fgh

Başlık dosyasının Sezar şifreleme uygulaması hakkında hiçbir şey ifşa etmediğine dikkat edin; Pimpl'ın gücü budur. Daha sonra farklı bir şifreleme algoritmasına geçseniz bile, başlığı dahil eden dosyalar değil, yalnızca SecureMessage.cpp dosyasının yeniden derlenmesi gerekir.

Kopya kağıdı

Pimpl deyimi (Pointer to Implementation - Uygulama İşaretçisi), uygulama ayrıntılarını yalnızca kaynak dosyada tanımlanan ayrı bir sınıfa taşıyarak gizler ve bir derleme güvenlik duvarı (compilation firewall) oluşturur.

Başlık dosyasında ön bildirim (forward declaration) ile temel yapı:

// Widget.h
#include <memory>

class Widget {
public:
    Widget();
    ~Widget();  // Başlık dosyasında bildirilmeli, .cpp dosyasında tanımlanmalıdır
    void doSomething();
    
private:
    class Impl;  // Ön bildirim
    std::unique_ptr<Impl> pImpl;
};

Kaynak dosyasındaki uygulama:

// Widget.cpp
#include "Widget.h"

class Widget::Impl {
public:
    int data = 42;
    void process() { /* uygulama */ }
};

Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;  // .cpp dosyasında tanımlanmalıdır

void Widget::doSomething() { pImpl->process(); }

Önemli noktalar:

  • Yıkıcı (destructor), kaynak dosyada tanımlanmalıdır (varsayılan olsa bile); çünkü unique_ptr, Impl nesnesini silmek için onun tam türüne (complete type) ihtiyaç duyar.
  • Impl üzerinde yapılan değişiklikler, başlık dosyasını dahil eden dosyaların değil, yalnızca kaynak dosyasının yeniden derlenmesini gerektirir.
  • Derleme güvenlik duvarı (compilation firewall) ve ikili uyumluluk (binary compatibility) sağlar.

Kendin dene

#include <iostream>
#include <string>
#include "SecureMessage.h"

int main() {
    // Mesajı oku (boşluk içerebilir)
    std::string message;
    std::getline(std::cin, message);
    
    // Şifreleme anahtarını oku
    int key;
    std::cin >> key;
    
    // TODO: Mesaj ile bir SecureMessage nesnesi oluşturun
    
    // TODO: Şifreleme anahtarını ayarlayın
    
    // TODO: "Original: " ve ardından orijinal mesajı yazdırın
    
    // TODO: "Encrypted: " ve ardından şifrelenmiş mesajı yazdı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