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.
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:SecureMessagesınıfınız için genel arayüzü tanımlayın.Sınıfınızda önceden bildirilmiş (forward-declared) bir
Implsınıfı ve ona işaret eden birstd::unique_ptrbulunmalı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::stringolarak döndüren birgetEncrypted()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.
- Orijinal mesaj için bir
SecureMessage.cpp: İç içe geçmişImplsınıfını tanımlayın ve tüm metotları uygulayın.Implsı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_ptrtamImpltürüne ihtiyaç duyduğundan, yıkıcıyı burada tanımlamayı (varsayılan olsa bile) unutmayın.main.cpp: İki girdi okuyun:- Bir mesaj dizesi (boşluk içerebilir)
- Bir şifreleme anahtarı (tam sayı)
Bir
SecureMessagenesnesi oluşturun, anahtarı ayarlayın ve sonuçları görüntüleyin:Original:ve ardından orijinal mesajı yazdırınEncrypted:ve ardından şifrelenmiş mesajı yazdırın
Örneğin, hello world ve 3 girdileriyle:
Original: hello world
Encrypted: khoor zruogxyz abc ve 5 girdileriyle:
Original: xyz abc
Encrypted: cde fghBaş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,Implnesnesini 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;
}
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
1OOP Temelleri
Harici DosyalarC++ Build ve DerlemeBaşlık Dosyaları ve Kaynak DosyalarıAd Alanları ve KapsamC++'ta OOP'ye GirişSınıflar ve Nesneler'this' İşaretçisiMetotlar (Üye Fonksiyonlar)Öznitelikler (Veri Üyeleri)Ctor ve Dtor TemelleriÖzet - Basit Hesap Makinesi4Sınıf Özellikleri
Örnek ve Statik ÜyelerGetter ve Setter MetotlarıConst Üye FonksiyonlarMutable Anahtar KelimesiStatik Metotlar ve DeğişkenlerFriend Fonksiyonlar ve SınıflarÖzet - Banka Hesabı Yöneticisi7Kalıtım
Temel KalıtımKalıtım Erişim SeviyeleriCtor ve Dtor Çağrılma SırasıMetot Geçersiz KılmaSanal Fonksiyonlar ve VTableÇoklu KalıtımSanal KalıtımÖzet - Çalışan Hiyerarşisi10STL Genel Bakış
STL Genel Bakış ve FelsefesiSTL KonteynerleriİteratörlerSTL AlgoritmalarıFunctor'lar ve Lambda İfadeleriÖzet - Kelime Frekansı13Tasarım Kalıpları 1. Bölüm
Tasarım Kalıplarına GirişSingleton KalıbıFactory ve Abstract FactoryBuilder KalıbıObserver KalıbıStrategy Kalıbı2Bellek Yönetimi
Stack ve Heap Bellekİşaretçiler ve ReferanslarDinamik Bellek (new/delete)C++'ta Akıllı İşaretçilerC++'ta RAIIÖzet - Dinamik Dizi Yöneticisi5Kapsülleme
C++'da Erişim BelirleyicilerDerinlemesine Erişim BelirleyicilerBilgi GizlemeStruct vs Classİç İçe ve Dahili SınıflarÖzet - Öğrenci Kayıt Sistemi8Çok Biçimlilik
Derleme ve Çalışma Zamanı Çok BiçimliliğiFonksiyon Aşırı YüklemeSanal Fonksiyonlara Yeniden BakışSaf Sanal FonksiyonlarSoyut SınıflarC++'ta Arayüz TasarımıDynamic Casting ve RTTIÖzet - Şekil Hesaplayıcı3Yapıcılar ve Yıkıcılar
Varsayılan YapıcıParametreli YapıcıKopya YapıcıTaşıma YapıcısıYapıcı İlklendirme ListeleriTemsilci YapıcılarYıkıcılara Derinlemesine BakışÜç / Beş / Sıfır KuralıÖzet - String Sınıfı6Operatör Aşırı Yükleme
Operatör Aşırı Yüklemeye GirişAritmetik Operatör Aşırı YüklemeKarşılaştırma Operatörü Aşırı YüklemeStream OperatörleriAtama Operatörü Aşırı Yükleme[] ve () Operatör Aşırı YüklemeTip Dönüşüm OperatörleriÖzet - Matris Sınıfı9Şablonlar
Fonksiyon ŞablonlarıSınıf ŞablonlarıŞablon ÖzelleştirmeVariadic ŞablonlarSFINAE ve Type Traits TemelleriÖzet - Generic Konteyner