Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> C++

Singleton Pattern ใน C++: การสร้าง Class ที่มี Instance เดียว

Singleton Pattern เป็น Design Pattern ที่จำกัดการสร้าง Instance ของ Class ให้มีเพียง หนึ่งเดียวตลอดชีวิตโปรแกรม ให้ Global Access Point ไปยัง Instance นั้น

Classic Implementation (Meyers Singleton - C++11 Thread-Safe)

#include <iostream>
#include <memory>

class Singleton {
private:
    int data_;
    
    // Private Constructor: ป้องกันการสร้างจากภายนอก
    Singleton() : data_(0) { 
        std::cout << "Singleton Constructed\n"; 
    }
    
    // Delete Copy/Move: ป้องกัน Copy
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;

public:
    // Static Access Method: Thread-Safe Initialization (C++11 Magic Static)
    static Singleton& getInstance() {
        static Singleton instance; // Guaranteed Thread-Safe Init (C++11)
        return instance;
    }
    
    int getData() const { return data_; }
    void setData(int val) { data_ = val; }
    
    // Optional: Destructor Control
    ~Singleton() { std::cout << "Singleton Destroyed\n"; }
};

int main() {
    Singleton& s1 = Singleton::getInstance();
    std::cout << "Data: " << s1.getData() << '\n'; // 0
    
    s1.setData(100);
    
    Singleton& s2 = Singleton::getInstance(); // Same Instance
    std::cout << "Data: " << s2.getData() << '\n'; // 100
    
    std::cout << "Same address? " << (&s1 == &s2) << '\n'; // 1 (true)
}

ผลลัพธ์


Singleton Constructed
Data: 0
Data: 100
Same address? 1
Singleton Destroyed

ทำไม Meyers Singleton ดีกว่า Classic Pointer Version?

ด้านClassic (Static Pointer)Meyers (Static Local)
Thread Safety (Init)Manual (Mutex/Double-Check)✅ Automatic (C++11)
Destruction OrderManual/Leak RiskAutomatic (Reverse Init Order)
Lazy InitializationManual✅ Automatic
Exception SafetyComplex✅ Automatic

การใช้งานจริง: Logger, Config, Database Pool


class Logger {
    std::ofstream logFile_;
    Logger() : logFile_("app.log", std::ios::app) {}
    
public:
    static Logger& instance() {
        static Logger logger;
        return logger;
    }
    
    void log(const std::string& msg) {
        logFile_ << "[" << currentTime() << "] " << msg << '\n';
    }
    
    // Delete Copy/Move
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
};

// Usage
Logger::instance().log("Application started");

ข้อเสียและการวิพากษ์ (Anti-Pattern?)

  • Global State: ยากต่อ Testing (Mocking ยาก)
  • Hidden Dependencies: Class ใช้ Singleton โดยไม่ปรากฏใน Signature
  • Tight Coupling
  • Destruction Order Issues (ถ้ามี Multiple Singletons อ้างอิงกัน)

ทางเลือก: Dependency Injection


// แทนที่จะเรียก Singleton::getInstance() ภายใน Class
class Service {
    Logger& logger_; // Injected
public:
    Service(Logger& logger) : logger_(logger) {}
    void doWork() { logger_.log("Working"); }
};

// Main: Wire Dependencies
int main() {
    Service service(Logger::instance()); // หรือ Mock Logger สำหรับ Test
}

สรุป

Meyers Singleton (static Local Variable ใน Function) เป็น Implementation ที่ดีที่สุดใน C++11+ (Thread-Safe, Exception-Safe, Lazy) แต่ควรพิจารณา Dependency Injection แทนสำหรับ Testability และ Loose Coupling ใช้ Singleton เฉพาะ Cross-cutting Concerns (Logging, Config, Hardware Access)