Menu
Coddy logo textTech

Idiom Pimpl

Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 78 z 104.

Idiom Pimpl (Pointer to Implementation) to technika ukrywająca szczegóły implementacji klasy poprzez przeniesienie ich do osobnej klasy zadeklarowanej w przód. Zmniejsza to zależności kompilacji i sprawia, że prywatne składowe pozostają naprawdę ukryte w pliku nagłówkowym.

Główna idea jest prosta: zamiast deklarować prywatne składowe bezpośrednio w swojej klasie, deklarujesz wskaźnik do klasy implementacyjnej zdefiniowanej wyłącznie w pliku źródłowym:

// Widget.h
#include <memory>

class Widget {
public:
    Widget();
    ~Widget();
    void doSomething();
    
private:
    class Impl;  // Deklaracja wyprzedzająca
    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(); }

Kluczowe korzyści to izolacja kompilacji i zgodność binarna. Gdy zmienisz klasę Impl, ponownej kompilacji wymaga tylko plik źródłowy — nie każdy plik, który dołącza plik nagłówkowy. To znacznie przyspiesza kompilację dużych projektów.

Pamiętaj, że destruktor musi być zdefiniowany w pliku źródłowym (nawet jeśli jest domyślny), ponieważ unique_ptr potrzebuje pełnego typu Impl, aby go usunąć. To częsta pułapka przy pierwszym użyciu Pimpl.

challenge icon

Wyzwanie

Łatwy

Stwórzmy moduł obsługi bezpiecznych wiadomości, wykorzystując idiom Pimpl do ukrycia szczegółów implementacji szyfrowania w pliku nagłówkowym. To pokazuje, jak Pimpl tworzy zaporę kompilacji: osoby dołączające Twój plik nagłówkowy nie zobaczą wewnętrznego działania przetwarzania wiadomości.

Rozmieścisz kod w trzech plikach:

  • SecureMessage.h: Zdefiniuj publiczny interfejs swojej klasy SecureMessage.

    Twoja klasa powinna mieć zadeklarowaną z wyprzedzeniem klasę Impl oraz wskaźnik std::unique_ptr do niej. Publiczny interfejs powinien zawierać:

    • Konstruktor przyjmujący const std::string& z oryginalną wiadomością
    • Destruktor (musi być zadeklarowany tutaj, a zdefiniowany w pliku .cpp)
    • Metodę setKey(int key) do ustawiania klucza szyfrowania
    • Metodę getEncrypted(), która zwraca zaszyfrowaną wiadomość jako std::string
    • Metodę getOriginal(), która zwraca oryginalną wiadomość

    Plik nagłówkowy powinien pokazywać wyłącznie publiczny interfejs: nie powinny być tu widoczne żadne szczegóły implementacji szyfrowania.

  • SecureMessage.cpp: Zdefiniuj zagnieżdżoną klasę Impl i zaimplementuj wszystkie metody.

    Klasa Impl powinna przechowywać oryginalną wiadomość i klucz szyfrowania (domyślnie 0), a także obsługiwać właściwą logikę szyfrowania. Do szyfrowania użyj prostego szyfru Cezara: przesuwaj każdy znak o wartość klucza. Na przykład przy kluczu 3 litera 'a' zmienia się w 'd', a 'z' zawija się i zmienia w 'c'.

    Szyfrowanie powinno dotyczyć wyłącznie małych liter (a-z), pozostawiając wszystkie pozostałe znaki bez zmian. Pamiętaj, aby zdefiniować tutaj destruktor (nawet jeśli jest domyślny), ponieważ unique_ptr wymaga kompletnego typu Impl.

  • main.cpp: Wczytaj dwa dane wejściowe:
    1. Ciąg znaków wiadomości (może zawierać spacje)
    2. Klucz szyfrowania (liczba całkowita)

    Utwórz obiekt SecureMessage, ustaw klucz i wyświetl wyniki:

    1. Wypisz Original: , a następnie oryginalną wiadomość
    2. Wypisz Encrypted: , a następnie zaszyfrowaną wiadomość

Na przykład dla danych wejściowych hello world i 3:

Original: hello world
Encrypted: khoor zruog

Dla danych wejściowych xyz abc i 5:

Original: xyz abc
Encrypted: cde fgh

Zauważ, że plik nagłówkowy nie ujawnia niczego na temat implementacji szyfru Cezara: na tym polega siła Pimpl. Jeśli później zmienisz algorytm szyfrowania, ponownej kompilacji będzie wymagał tylko SecureMessage.cpp, a nie żaden plik dołączający plik nagłówkowy.

Spróbuj swoich sił

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

int main() {
    // Wczytaj wiadomość (może zawierać spacje)
    std::string message;
    std::getline(std::cin, message);
    
    // Wczytaj klucz szyfrowania
    int key;
    std::cin >> key;
    
    // TODO: Utwórz obiekt SecureMessage z wiadomością
    
    // TODO: Ustaw klucz szyfrowania
    
    // TODO: Wypisz "Original: ", a następnie oryginalną wiadomość
    
    // TODO: Wypisz "Encrypted: ", a następnie zaszyfrowaną wiadomość
    
    return 0;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online