स्टैटिक मेंबर, सिंगलटन और PIMPL
स्टैटिक मेंबर, Meyers Singleton पैटर्न, Factory पैटर्न, और ABI स्थिरता तथा कंपाइल-टाइम इन्सुलेशन के लिए PIMPL idiom को जानें।
बुनियादी क्लास से आगे
जब आप क्लास, कंस्ट्रक्टर और इनहेरिटेंस में सहज हो जाते हैं, तो आपको डिज़ाइन पैटर्न की ज़रूरत पड़ती है — बार-बार आने वाली समस्याओं के आज़माए हुए समाधान। यह पाठ तीन महत्वपूर्ण पैटर्न कवर करता है: Singleton (नियंत्रित वैश्विक पहुँच), Factory (उपयोग से अलग किया गया ऑब्जेक्ट निर्माण) और PIMPL (कंपाइलेशन फ़ायरवॉल और ABI स्थिरता)। हम स्टैटिक मेंबर और उनके आधुनिक C++17 इनलाइन इनिशियलाइज़ेशन को भी दोबारा देखते हैं।
स्टैटिक मेंबर और Inline Static (C++17)
स्टैटिक मेंबर वेरिएबल क्लास के सभी इंस्टेंस में साझा होते हैं — चाहे कितने भी ऑब्जेक्ट हों, उसकी ठीक एक ही कॉपी रहती है। C++17 से पहले, स्टैटिक मेंबर को .cpp फ़ाइल में परिभाषित करना पड़ता था। C++17 का inline static सीधे हेडर में डेफ़िनिशन की अनुमति देता है।
#include <iostream>
#include <string>
#include <vector>
class Logger {
// C++17: inline static — no separate .cpp definition needed
inline static std::vector<std::string> messages_;
inline static int error_count_ = 0;
public:
// Static member function — no 'this' pointer, can only
// access static members
static void log(const std::string& msg) {
messages_.push_back(msg);
}
static void error(const std::string& msg) {
messages_.push_back("[ERROR] " + msg);
++error_count_;
}
static void dump() {
for (const auto& m : messages_) {
std::cout << m << '\n';
}
std::cout << "Total errors: " << error_count_ << '\n';
}
static int error_count() { return error_count_; }
};
int main() {
Logger::log("Application started");
Logger::error("File not found");
Logger::log("Retrying...");
Logger::error("Permission denied");
Logger::dump();
return 0;
}Meyers Singleton (थ्रेड-सेफ़)
Meyers Singleton फ़ंक्शन-लोकल स्टैटिक वेरिएबल का उपयोग करता है, जिसके बारे में C++11 गारंटी देता है कि वह थ्रेड-सेफ़ ढंग से इनिशियलाइज़ होता है। आधुनिक C++ में सिंगलटन लागू करने का यह सबसे सरल और सुरक्षित तरीका है।
#include <iostream>
#include <string>
class AppConfig {
public:
// Delete copy and move to prevent duplication
AppConfig(const AppConfig&) = delete;
AppConfig& operator=(const AppConfig&) = delete;
// Meyers Singleton: function-local static
static AppConfig& instance() {
static AppConfig config; // thread-safe since C++11
return config;
}
void set_debug(bool d) { debug_ = d; }
bool debug() const { return debug_; }
void set_app_name(const std::string& name) { app_name_ = name; }
const std::string& app_name() const { return app_name_; }
private:
// Private constructor — only instance() can create
AppConfig() : debug_{false}, app_name_{"MyApp"} {}
bool debug_;
std::string app_name_;
};
int main() {
auto& config = AppConfig::instance();
config.set_app_name("SuperApp");
config.set_debug(true);
// Same instance everywhere
std::cout << AppConfig::instance().app_name() << '\n';
std::cout << std::boolalpha
<< AppConfig::instance().debug() << '\n';
return 0;
}Factory पैटर्न
Factory पैटर्न ऑब्जेक्ट निर्माण को उसके उपयोग से अलग करता है। कॉलर बताते हैं कि उन्हें क्या चाहिए (जैसे शेप का टाइप), और फ़ैक्टरी तय करती है कि किस कंक्रीट क्लास का इंस्टेंस बनाना है। यह हर निर्माण-स्थल पर if/else या switch स्टेटमेंट की श्रृंखला से अधिक साफ़ है।
#include <iostream>
#include <memory>
#include <string>
#include <stdexcept>
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
virtual std::string name() const = 0;
};
class Circle : public Shape {
double r_;
public:
explicit Circle(double r) : r_{r} {}
double area() const override { return 3.14159265 * r_ * r_; }
std::string name() const override { return "Circle"; }
};
class Square : public Shape {
double side_;
public:
explicit Square(double s) : side_{s} {}
double area() const override { return side_ * side_; }
std::string name() const override { return "Square"; }
};
// Factory function — callers don't need to know concrete types
[[nodiscard]]
std::unique_ptr<Shape> make_shape(const std::string& type, double size) {
if (type == "circle") return std::make_unique<Circle>(size);
if (type == "square") return std::make_unique<Square>(size);
throw std::invalid_argument("Unknown shape: " + type);
}
int main() {
auto s1 = make_shape("circle", 5.0);
auto s2 = make_shape("square", 4.0);
std::cout << s1->name() << ": " << s1->area() << '\n';
std::cout << s2->name() << ": " << s2->area() << '\n';
return 0;
}PIMPL Idiom (इम्प्लीमेंटेशन का पॉइंटर)
PIMPL (Pointer to Implementation) idiom इम्प्लीमेंटेशन के ब्योरे को एक ओपेक पॉइंटर के पीछे छिपा देता है। लाभ: (1) इम्प्लीमेंटेशन बदलने पर क्लाइंट को दोबारा कंपाइल नहीं करना पड़ता, (2) private मेंबर हेडर में दिखाई नहीं देते (ABI स्थिरता), (3) तेज़ कंपाइलेशन, क्योंकि इम्प्लीमेंटेशन के हेडर केवल .cpp फ़ाइल में चाहिए।
// ========== widget.hpp ==========
#include <memory>
#include <string>
class Widget {
public:
Widget(const std::string& name, int value);
~Widget(); // must be declared; defined in .cpp
// Move operations — defined in .cpp where Impl is complete
Widget(Widget&&) noexcept;
Widget& operator=(Widget&&) noexcept;
// No copy (or implement deep copy in .cpp)
Widget(const Widget&) = delete;
Widget& operator=(const Widget&) = delete;
void do_work() const;
std::string name() const;
private:
struct Impl; // forward declaration only!
std::unique_ptr<Impl> pimpl_; // opaque pointer
};
// ========== widget.cpp ==========
// #include "widget.hpp"
#include <iostream>
struct Widget::Impl {
std::string name;
int value;
// Any complex private members go here
// Changes here do NOT affect the header!
};
Widget::Widget(const std::string& name, int value)
: pimpl_{std::make_unique<Impl>(Impl{name, value})} {}
Widget::~Widget() = default; // Impl is complete here
Widget::Widget(Widget&&) noexcept = default;
Widget& Widget::operator=(Widget&&) noexcept = default;
void Widget::do_work() const {
std::cout << "Working on: " << pimpl_->name
<< " (value=" << pimpl_->value << ")\n";
}
std::string Widget::name() const { return pimpl_->name; }
int main() {
Widget w{"Engine", 42};
w.do_work();
std::cout << w.name() << '\n';
return 0;
}सिंगलटन असल में बेहतर इंटरफ़ेस वाली ग्लोबल अवस्था हैं। वे टेस्टिंग कठिन बनाते हैं (रीसेट या मॉक करना मुश्किल), छिपी निर्भरताएँ पैदा करते हैं और मल्टी-थ्रेडेड कोड को जटिल बनाते हैं। इन्हें केवल तभी इस्तेमाल करें जब सचमुच एक अकेले, वैश्विक रूप से सुलभ इंस्टेंस की ज़रूरत हो — जैसे हार्डवेयर ड्राइवर या एप्लिकेशन कॉन्फ़िगरेशन। अगर आप खुद को कई सिंगलटन बनाते हुए पाएँ, तो अपने डिज़ाइन पर दोबारा विचार करें और निर्भरताओं को स्पष्ट रूप से पास करें।
PIMPL का उपयोग लाइब्रेरी इंटरफ़ेस के लिए करें जहाँ ABI स्थिरता मायने रखती है — PIMPL के बिना private मेंबर बदलने से क्लास का लेआउट बदल जाता है, जिससे बाइनरी संगतता टूटती है। PIMPL हेडर में इम्प्लीमेंटेशन छिपाकर बड़े प्रोजेक्ट में कंपाइलेशन समय भी काफ़ी घटा देता है। इसकी कीमत है एक अतिरिक्त हीप आवंटन और प्रति कॉल एक पॉइंटर इनडायरेक्शन। परफ़ॉर्मेंस-क्रिटिकल इनर-लूप कोड के लिए PIMPL उपयुक्त नहीं हो सकता।
- C++17 का
inline staticआपको स्टैटिक मेंबर सीधे क्लास हेडर में परिभाषित करने देता है - Meyers Singleton फ़ंक्शन-लोकल स्टैटिक का उपयोग करता है — C++11 से थ्रेड-सेफ़
- Factory पैटर्न ऑब्जेक्ट निर्माण को उपयोग से अलग करता है, जिससे विस्तारशीलता बेहतर होती है
- PIMPL इम्प्लीमेंटेशन के ब्योरे को छिपाता है, जिससे ABI स्थिरता और तेज़ कंपाइलेशन मिलता है
- सिंगलटन भेस में छिपी ग्लोबल अवस्था हैं — इन्हें कम से कम इस्तेमाल करें और डिपेंडेंसी इंजेक्शन को प्राथमिकता दें
- फ़ैक्टरी फ़ंक्शन पर
[[nodiscard]]कॉलर को बनाए गए ऑब्जेक्ट को गलती से अनदेखा करने से रोकता है
क्विज़ — अपना ज्ञान परखें
(15 XP)1. Meyers Singleton C++11 और उसके बाद थ्रेड-सेफ़ क्यों है?
2. PIMPL idiom का मुख्य लाभ क्या है?
3. PIMPL डिस्ट्रक्टर को हेडर में नहीं, .cpp फ़ाइल में ही क्यों परिभाषित करना चाहिए?