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

एरर हैंडलिंग रणनीतियाँ: सही तरीका चुनना

एक्सेप्शन, एरर कोड और std::expected की तुलना करें — जानें कि कौन-सा तरीका कब उपयुक्त है और कंस्ट्रक्टर, डिस्ट्रक्टर और मल्टीथ्रेडेड कोड में एरर कैसे हैंडल करें।

सबके लिए एक ही समाधान नहीं

C++ कई एरर हैंडलिंग मैकेनिज़्म देता है, और हर एक के अपने ट्रेडऑफ़ हैं। सही मैकेनिज़्म चुनना आपके डोमेन पर निर्भर करता है:

एक्सेप्शन उन एरर के लिए सबसे अच्छे हैं जो दुर्लभ हैं और स्थानीय रूप से हैंडल नहीं किए जा सकते। सफल पथ पर इनकी लागत शून्य है, लेकिन थ्रो होने पर ये महँगे हैं (हज़ारों नैनोसेकंड)। ये कॉल फ़्रेम के पार अपने-आप प्रोपेगेट होते हैं।

एरर कोड (रिटर्न वैल्यू, errno, std::error_code) उन एरर के लिए सबसे अच्छे हैं जो अपेक्षित और बार-बार होने वाले हैं। हर कॉल पर इनकी एक छोटी, स्थिर लागत होती है (कॉलर को रिटर्न वैल्यू जाँचनी पड़ती है)। C API और कर्नेल-स्तर के कोड में यही आम तरीका है।

std::expected (C++23) दोनों की अच्छाइयों को जोड़ता है: यह कॉलर को एरर स्वीकार करने के लिए बाध्य करता है (एरर कोड की तरह), लेकिन विस्तृत एरर टाइप साथ रखता है (एक्सेप्शन की तरह) और मोनैडिक चेनिंग का समर्थन करता है। नए C++ कोड में यह तेज़ी से पसंदीदा तरीका बनता जा रहा है।

मुख्य बात: ये आपस में प्रतिस्पर्धी दर्शन नहीं हैं — ये अलग-अलग स्थितियों के लिए औज़ार हैं, और प्रोडक्शन कोड अक्सर तीनों का उपयोग करता है।

तुलना: एक्सेप्शन बनाम एरर कोड बनाम std::expected

| मानदंड | एक्सेप्शन | एरर कोड | std::expected |
|---|---|---|---|
| सफलता पर लागत | शून्य (टेबल-आधारित) | ब्रांच + जाँच | न्यूनतम (वैल्यू स्टोर करता है) |
| एरर पर लागत | अधिक (अनवाइंडिंग) | सफलता जितनी ही | सफलता जितनी ही |
| क्या अनदेखा किया जा सकता है? | नहीं (प्रोग्राम समाप्त हो जाता है) | हाँ (चुपचाप बग) | अधिक कठिन (.value() एक्सेस करना पड़ता है) |
| कंस्ट्रक्टर | एकमात्र विकल्प | वैल्यू नहीं लौटा सकते | वैल्यू नहीं लौटा सकते |
| डिस्ट्रक्टर | थ्रो नहीं करना चाहिए | ठीक | ठीक |
| कंपोज़ेबिलिटी | सीमित (try/catch) | मैन्युअल if-चेन | मोनैडिक (and_then, transform) |
| बाइनरी साइज़ | बड़ा (अनवाइंड टेबल) | न्यूनतम | न्यूनतम |
| रियल-टाइम के लिए सुरक्षित? | नहीं (असीमित समय) | हाँ | हाँ |

कंस्ट्रक्टर में एरर हैंडलिंग

कंस्ट्रक्टर एरर कोड नहीं लौटा सकते — उनकी कोई रिटर्न वैल्यू नहीं होती। निर्माण की विफलता बताने के लिए एक्सेप्शन ही एकमात्र साफ़ मैकेनिज़्म हैं। थ्रो हुए एक्सेप्शन का अर्थ है कि ऑब्जेक्ट कभी पूरी तरह निर्मित नहीं हुआ, इसलिए उसका डिस्ट्रक्टर कभी कॉल नहीं होता (लेकिन पहले से निर्मित सब-ऑब्जेक्ट और बेस के डिस्ट्रक्टर कॉल होते हैं)।

constructor_error.cpp
#include <fstream>
#include <string>
#include <stdexcept>
#include <iostream>

class ConfigFile {
public:
    explicit ConfigFile(const std::string& path)
        : path_(path), stream_(path) {
        if (!stream_.is_open()) {
            throw std::runtime_error(
                "Cannot open config file: " + path);
        }
        // Parse the file — if this throws, stream_ is
        // already constructed and its destructor will
        // close the file automatically (RAII)
        parse();
    }

    std::string get(const std::string& key) const {
        auto it = data_.find(key);
        if (it == data_.end()) {
            throw std::out_of_range("Key not found: " + key);
        }
        return it->second;
    }

private:
    void parse() {
        std::string line;
        while (std::getline(stream_, line)) {
            auto pos = line.find('=');
            if (pos != std::string::npos) {
                data_[line.substr(0, pos)] = line.substr(pos + 1);
            }
        }
    }

    std::string path_;
    std::ifstream stream_;  // RAII: closed by destructor
    std::map<std::string, std::string> data_;
};

// Usage: the object is fully valid or does not exist
int main() {
    try {
        ConfigFile cfg("/etc/myapp.conf");
        std::cout << cfg.get("port") << '\n';
    } catch (const std::exception& e) {
        std::cerr << e.what() << '\n';
    }
}

सिस्टम-स्तर की एरर के लिए std::error_code

std::error_code सिस्टम-स्तर की एरर के लिए एक हल्का, थ्रो न करने वाला मैकेनिज़्म देता है। लाइब्रेरी इसका बढ़िया उदाहरण है — अधिकांश फ़ंक्शन में थ्रो करने के बजाय std::error_code& पैरामीटर लेने वाला ओवरलोड होता है।

error_code.cpp
#include <filesystem>
#include <iostream>
#include <system_error>

namespace fs = std::filesystem;

// Throwing version — suitable for unexpected errors
void copy_file_throwing(const fs::path& src, const fs::path& dst) {
    fs::copy_file(src, dst);  // throws fs::filesystem_error on failure
}

// Non-throwing version — suitable for expected failures
bool copy_file_safe(const fs::path& src, const fs::path& dst) {
    std::error_code ec;
    fs::copy_file(src, dst, ec);  // sets ec instead of throwing
    if (ec) {
        std::cerr << "Copy failed: " << ec.message()
                  << " (" << ec.category().name()
                  << ':' << ec.value() << ")\n";
        return false;
    }
    return true;
}

int main() {
    // The error_code version is preferable in loops where
    // failure is expected (e.g., batch file processing)
    for (const auto& entry : fs::directory_iterator(".")) {
        std::error_code ec;
        auto sz = fs::file_size(entry.path(), ec);
        if (!ec) {
            std::cout << entry.path().filename()
                      << ": " << sz << " bytes\n";
        }
    }
}
सामान्य गलती

C++11 और बाद के संस्करणों में डिस्ट्रक्टर अप्रत्यक्ष रूप से noexcept होते हैं। यदि कोई डिस्ट्रक्टर थ्रो करता है, तो std::terminate() कॉल होता है। भले ही आप किसी डिस्ट्रक्टर को स्पष्ट रूप से noexcept(false) चिह्नित करें, स्टैक अनवाइंडिंग के दौरान (जब कोई दूसरा एक्सेप्शन पहले से सक्रिय हो) थ्रो करने पर std::terminate() कॉल होता है।

  • लॉग करें और दबा दें: यदि कोई क्लीनअप ऑपरेशन विफल हो, तो एरर लॉग करें और आगे बढ़ें
  • अलग close() मेथड दें: कॉलर को डिस्ट्रक्शन से पहले क्लीनअप एरर को स्पष्ट रूप से हैंडल करने दें
  • रिसोर्स ऐसे डिज़ाइन करें कि क्लीनअप विफल न हो सके: उदाहरण के लिए, फ़ाइल डिस्क्रिप्टर बंद करना OS स्तर पर हमेशा सफल होता है
  • डिस्ट्रक्टर से थ्रो करने वाला फ़ंक्शन कभी कॉल न करें, जब तक उसे try/catch में न लपेटा गया हो

मल्टीथ्रेडेड कोड में एरर हैंडलिंग

एक्सेप्शन थ्रेड की सीमाएँ पार नहीं करते। यदि कोई थ्रेड थ्रो करता है और एक्सेप्शन उसी थ्रेड के भीतर catch नहीं होता, तो std::terminate() कॉल होता है। थ्रेड के बीच एक्सेप्शन प्रोपेगेट करने के लिए:

1. std::future और std::async का उपयोग करें: async टास्क में थ्रो हुए एक्सेप्शन कैप्चर हो जाते हैं और फ़्यूचर पर .get() कॉल करने पर दोबारा थ्रो होते हैं।
2. एक्सेप्शन को मैन्युअली कैप्चर करने और ट्रांसपोर्ट करने के लिए std::current_exception() और std::rethrow_exception() के साथ std::exception_ptr का उपयोग करें।
3. प्रोड्यूसर थ्रेड से कंज़्यूमर को एक्सेप्शन भेजने के लिए std::promise::set_exception() का उपयोग करें।

हाई-थ्रूपुट कंकरेंट कोड में अक्सर एरर कोड या std::expected को प्राथमिकता दी जाती है, क्योंकि वे हर थ्रेड में एक्सेप्शन इन्फ्रास्ट्रक्चर की लागत से बचाते हैं और थ्रेड सीमाओं के पार उन्हें समझना भी आसान होता है।

सर्वोत्तम अभ्यास

एरर हैंडलिंग मैकेनिज़्म को स्थिति के अनुसार चुनें।

  • एक्सेप्शन का उपयोग करें उन एरर के लिए जो दुर्लभ हैं, स्थानीय रूप से हैंडल नहीं किए जा सकते, और जिन्हें स्टैक में ऊपर तक प्रोपेगेट होना चाहिए (जैसे, फ़ाइल न मिलना, डेटाबेस बंद होना)
  • एरर कोड का उपयोग करें उच्च-आवृत्ति वाले ऑपरेशन के लिए जहाँ विफलता सामान्य और अपेक्षित है (जैसे, कैश लुकअप मिस, नॉन-ब्लॉकिंग I/O का would-block)
  • std::expected का उपयोग करें नए कोड में, जहाँ आप विस्तृत एरर टाइप और मोनैडिक कंपोज़िशन के साथ स्पष्ट एरर हैंडलिंग चाहते हैं
  • कंस्ट्रक्टर में एक्सेप्शन का उपयोग करें — ऑब्जेक्ट के अस्तित्व में आने से पहले विफलता बताने का यही एकमात्र तरीका है
  • डिस्ट्रक्टर में कभी थ्रो न करें — एरर को लॉग करें और दबा दें
मुख्य बातें
  • एक्सेप्शन, एरर कोड और std::expected एक-दूसरे के पूरक औज़ार हैं — प्रतिस्पर्धी दर्शन नहीं
  • कंस्ट्रक्टर को विफलता बताने के लिए एक्सेप्शन का ही उपयोग करना चाहिए; एरर कोड विकल्प नहीं हैं
  • डिस्ट्रक्टर को कभी थ्रो नहीं करना चाहिए — C++11 से वे अप्रत्यक्ष रूप से noexcept हैं
  • एक्सेप्शन थ्रेड की सीमाएँ पार नहीं करते — उन्हें ट्रांसफ़र करने के लिए std::future या std::exception_ptr का उपयोग करें
  • std::error_code सिस्टम-स्तर की एरर के लिए आदर्श है, जहाँ आप थ्रो न करने वाला API चाहते हैं

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

(15 XP)

1. कंस्ट्रक्टर में एक्सेप्शन को पसंदीदा एरर हैंडलिंग मैकेनिज़्म क्यों माना जाता है?

2. जब किसी दूसरे एक्सेप्शन से स्टैक अनवाइंडिंग चल रही हो और उसी दौरान डिस्ट्रक्टर में एक्सेप्शन थ्रो हो जाए, तो क्या होता है?

3. थ्रेड के बीच एक्सेप्शन कैसे ट्रांसफ़र किए जा सकते हैं?