सामग्री पर जाएं

क्लास, स्ट्रक्ट और एनकैप्सुलेशन

struct और class का अंतर, एक्सेस स्पेसिफ़ायर, एनकैप्सुलेशन, const मेंबर फ़ंक्शन, स्टैटिक मेंबर और आधुनिक ऐग्रीगेट इनिशियलाइज़ेशन को समझें।

क्लास (class) क्या है?

C++ में क्लास (class) एक यूज़र-डिफ़ाइंड टाइप है, जो डेटा (मेंबर वेरिएबल) और उस डेटा पर होने वाले ऑपरेशन (मेंबर फ़ंक्शन) को एक ही इकाई में बाँध देती है। इस बाँधने को एनकैप्सुलेशन (encapsulation) कहते हैं — यह OOP के चार स्तंभों में से एक है।

मुख्य बात यह है कि क्लास एक इन्वेरिएंट (invariant) तय करती है: यानी ऐसे नियमों का समूह जो उसकी आंतरिक अवस्था (state) के बारे में हमेशा सत्य रहने चाहिए। उदाहरण के लिए, Date क्लास यह शर्त रख सकती है कि महीना हमेशा 1 से 12 के बीच हो। एनकैप्सुलेशन आंतरिक अवस्था तक पहुँच को नियंत्रित करके आपको इन इन्वेरिएंट को लागू रखने देता है।

struct बनाम class

C++ में struct और class लगभग एक जैसे हैं। एकमात्र अंतर डिफ़ॉल्ट एक्सेस स्पेसिफ़ायर का है: struct में डिफ़ॉल्ट public होता है, जबकि class में डिफ़ॉल्ट private। परंपरा यह है: सादे डेटा के समूह (aggregate) के लिए struct और जब आपको कोई इन्वेरिएंट बनाए रखना हो तब class इस्तेमाल करें।

struct_vs_class.cpp
#include <iostream>
#include <string>

// struct: members are public by default
struct Point {
    double x;  // public
    double y;  // public
};

// class: members are private by default
class BankAccount {
    std::string owner;   // private
    double balance;      // private

public:
    BankAccount(const std::string& name, double initial)
        : owner{name}, balance{initial} {}

    double get_balance() const { return balance; }

    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }
};

int main() {
    Point p{3.0, 4.0};  // aggregate initialization
    std::cout << p.x << ", " << p.y << '\n';

    BankAccount acct{"Alice", 1000.0};
    acct.deposit(500.0);
    std::cout << acct.get_balance() << '\n';  // 1500
    // acct.balance = -999;  // ERROR: private member

    return 0;
}

एक्सेस स्पेसिफ़ायर और this पॉइंटर

C++ तीन एक्सेस लेवल देता है: public (कोई भी एक्सेस कर सकता है), private (केवल क्लास और उसके फ़्रेंड) और protected (क्लास, उसके फ़्रेंड और डिराइव्ड क्लास)। this पॉइंटर वर्तमान ऑब्जेक्ट की ओर इशारा करने वाला एक इम्प्लिसिट पॉइंटर है, जो हर नॉन-स्टैटिक मेंबर फ़ंक्शन के अंदर उपलब्ध होता है।

access_specifiers.cpp
#include <iostream>
#include <string>

class Employee {
private:
    std::string name_;
    int id_;

protected:
    double salary_;  // accessible to derived classes

public:
    Employee(const std::string& name, int id, double salary)
        : name_{name}, id_{id}, salary_{salary} {}

    // const member function: promises not to modify the object
    const std::string& get_name() const { return name_; }
    int get_id() const { return id_; }

    // 'this' pointer usage — return *this for method chaining
    Employee& set_name(const std::string& name) {
        this->name_ = name;  // 'this->' disambiguates
        return *this;
    }

    Employee& set_id(int id) {
        this->id_ = id;
        return *this;
    }

    void print() const {
        std::cout << "Employee{" << name_ << ", #" << id_
                  << ", $" << salary_ << "}\n";
    }
};

int main() {
    Employee e{"Bob", 42, 75000.0};
    e.set_name("Robert").set_id(43);  // method chaining via *this
    e.print();  // Employee{Robert, #43, $75000}
    return 0;
}

स्टैटिक मेंबर और friend

स्टैटिक मेंबर क्लास के होते हैं, किसी एक इंस्टेंस के नहीं। इन्हें साझा अवस्था या यूटिलिटी फ़ंक्शन के लिए इस्तेमाल करें। friend कीवर्ड किसी नॉन-मेंबर फ़ंक्शन या दूसरी क्लास को private मेंबर तक पहुँच देता है — इसका उपयोग कम से कम करें, क्योंकि यह एनकैप्सुलेशन को तोड़ता है।

static_friend.cpp
#include <iostream>

class Widget {
    int value_;

    // C++17: inline static — define in header, no .cpp needed
    inline static int count_ = 0;

public:
    explicit Widget(int v) : value_{v} { ++count_; }
    ~Widget() { --count_; }

    static int get_count() { return count_; }

    // friend function can access private members
    friend std::ostream& operator<<(std::ostream& os, const Widget& w);
};

std::ostream& operator<<(std::ostream& os, const Widget& w) {
    return os << "Widget(" << w.value_ << ')';  // accesses private value_
}

int main() {
    Widget a{10}, b{20};
    std::cout << Widget::get_count() << '\n';  // 2
    std::cout << a << '\n';  // Widget(10)
    {
        Widget c{30};
        std::cout << Widget::get_count() << '\n';  // 3
    }
    std::cout << Widget::get_count() << '\n';  // 2
    return 0;
}

ऐग्रीगेट इनिशियलाइज़ेशन और डेज़िग्नेटेड इनिशियलाइज़र (C++20)

ऐग्रीगेट (aggregate) ऐसी क्लास या स्ट्रक्ट होती है जिसमें कोई यूज़र-डिक्लेअर्ड कंस्ट्रक्टर नहीं होता, कोई private/protected नॉन-स्टैटिक डेटा मेंबर नहीं होता, कोई वर्चुअल फ़ंक्शन नहीं होता और कोई वर्चुअल बेस क्लास नहीं होती। C++20 डेज़िग्नेटेड इनिशियलाइज़र (designated initializers) जोड़ता है, जिनसे आप बता सकते हैं कि आप किन मेंबर को इनिशियलाइज़ कर रहे हैं।

designated_init.cpp
#include <iostream>
#include <string>

struct Config {
    std::string host = "localhost";
    int port = 8080;
    bool use_tls = false;
    int max_connections = 100;
};

int main() {
    // Traditional aggregate initialization
    Config c1{"example.com", 443, true, 200};

    // C++20 designated initializers — much more readable
    Config c2{
        .host = "api.example.com",
        .port = 443,
        .use_tls = true
        // .max_connections uses default value 100
    };

    std::cout << c2.host << ':' << c2.port << '\n';
    std::cout << "TLS: " << std::boolalpha << c2.use_tls << '\n';
    std::cout << "Max: " << c2.max_connections << '\n';  // 100
    return 0;
}
सर्वोत्तम अभ्यास

डेटा मेंबर को डिफ़ॉल्ट रूप से private रखें और केवल वही चीज़ें उजागर करें जिनकी ज़रूरत है, एक न्यूनतम public इंटरफ़ेस के ज़रिए। अगर किसी क्लास में बनाए रखने के लिए कोई इन्वेरिएंट नहीं है (वह केवल डेटा का समूह है), तो public मेंबर वाला struct इस्तेमाल करें। हर फ़ील्ड के लिए गेटर और सेटर न जोड़ें — इससे एनकैप्सुलेशन का उद्देश्य ही समाप्त हो जाता है। इसके बजाय set_balance() जैसे फ़ंक्शन की जगह deposit() और withdraw() जैसे अर्थपूर्ण ऑपरेशन दें।

सामान्य गलती

अगर कोई मेंबर फ़ंक्शन ऑब्जेक्ट की अवस्था को नहीं बदलता, तो उसे const चिह्नित करें। const के बिना आप उस फ़ंक्शन को const ऑब्जेक्ट पर या const रेफ़रेंस के ज़रिए कॉल नहीं कर सकते — जो तब हर समय होता है जब ऑब्जेक्ट const& से पास किए जाते हैं। एक्सेसर पर const छूट जाना बड़े कोडबेस में कंपाइलेशन एरर का आम कारण है।

मुख्य बातें
  • struct और class केवल डिफ़ॉल्ट एक्सेस में भिन्न हैं: struct public होता है, class private
  • एनकैप्सुलेशन क्लास इन्वेरिएंट की रक्षा करता है — private डेटा के साथ एक अर्थपूर्ण public इंटरफ़ेस इस्तेमाल करें
  • जो मेंबर फ़ंक्शन ऑब्जेक्ट को नहीं बदलते उन्हें const चिह्नित करें, ताकि वे const रेफ़रेंस के साथ काम करें
  • हेडर में परिभाषित स्टैटिक मेंबर के लिए inline static (C++17) इस्तेमाल करें
  • C++20 के डेज़िग्नेटेड इनिशियलाइज़र ऐग्रीगेट इनिशियलाइज़ेशन को पढ़ने में आसान और स्वयं-व्याख्यात्मक बनाते हैं
  • friend का उपयोग कम से कम करें — आमतौर पर केवल उन ऑपरेटर ओवरलोड के लिए जिन्हें private एक्सेस चाहिए

क्विज़ — अपना ज्ञान परखें

(15 XP)

1. C++ में `struct` और `class` के बीच एकमात्र अंतर क्या है?

2. एक्सेसर मेंबर फ़ंक्शन को `const` क्यों चिह्नित करना चाहिए?

3. C++17 में `inline static int count_ = 0;` से क्या हासिल होता है?