फ़ंक्शन और पैरामीटर पासिंग
C++ में आर्ग्युमेंट पास करने के अलग-अलग तरीकों में महारत हासिल करें, और रिटर्न वैल्यू ऑप्टिमाइज़ेशन, फ़ंक्शन ओवरलोडिंग और [[nodiscard]] जैसे आधुनिक एट्रिब्यूट को समझें।
फ़ंक्शन: मूल एब्स्ट्रैक्शन
फ़ंक्शन C++ में कोड संगठन की बुनियादी इकाई हैं। कई दूसरी भाषाओं के विपरीत, C++ आपको इस पर बारीक नियंत्रण देती है कि आर्ग्युमेंट फ़ंक्शन को कैसे पास किए जाएँ और फ़ंक्शन से कैसे लौटाए जाएँ। यह नियंत्रण सिर्फ़ सिंटैक्टिक शुगर नहीं है — इसके परफ़ॉर्मेंस पर असली प्रभाव पड़ते हैं।
आप पैरामीटर कैसे पास करते हैं, यही तय करता है कि फ़ंक्शन आर्ग्युमेंट की कॉपी के साथ काम करेगा, मूल के रेफ़रेंस के साथ, या मूल के पॉइंटर के साथ। सही तंत्र चुनना फ़ंक्शन का इंटरफ़ेस डिज़ाइन करते समय आपके सबसे अहम फ़ैसलों में से एक है।
पैरामीटर पासिंग के तंत्र
C++ आर्ग्युमेंट पास करने के चार प्राथमिक तरीके सपोर्ट करती है। परफ़ॉर्मेंस, सुरक्षा और सिमेंटिक्स के लिहाज़ से हर एक के अलग ट्रेड-ऑफ़ हैं:
#include <iostream>
#include <string>
#include <vector>
// 1. Pass by VALUE — the function gets a copy
// Use for: small types (int, double, char), when you need a local copy
void square(int x) { // x is a copy of the argument
x = x * x; // Modifying x does NOT affect the caller
std::cout << "Inside: " << x << '\n';
}
// 2. Pass by REFERENCE — the function works on the original
// Use for: when you need to modify the caller's variable
void double_it(int& x) { // x IS the caller's variable
x *= 2; // This DOES modify the caller's variable
}
// 3. Pass by CONST REFERENCE — read-only access to the original, no copy
// Use for: large objects you only need to read
void print_all(const std::vector<std::string>& names) {
for (const auto& name : names) {
std::cout << name << '\n';
}
// names.push_back("Eve"); // ERROR: names is const
}
// 4. Pass by POINTER — explicit nullable indirection
// Use for: optional parameters (can be nullptr), C API interop
void maybe_increment(int* ptr) {
if (ptr != nullptr) { // Must check for null!
++(*ptr);
}
}
int main() {
int val = 5;
square(val);
std::cout << "After square: " << val << '\n'; // Still 5 (copied)
double_it(val);
std::cout << "After double_it: " << val << '\n'; // Now 10 (modified)
std::vector<std::string> people = {"Alice", "Bob", "Charlie"};
print_all(people); // No copy of the vector — efficient
maybe_increment(&val); // Pass address explicitly
std::cout << "After maybe_increment: " << val << '\n'; // 11
maybe_increment(nullptr); // Safe: the function checks for null
}इस उदाहरण को कंपाइल करके चलाने के लिए चलाएं दबाएं।
पैरामीटर कैसे पास करें, यह चुनने के लिए C++ Core Guidelines के इन दिशानिर्देशों का पालन करें:
- कॉपी करना सस्ता है और कभी बदला नहीं जाता? वैल्यू से पास करें (int, double, char, bool, छोटे स्ट्रक्ट, std::string_view)।
- कॉपी करना महँगा है और केवल पढ़ा जाता है? const& से पास करें (std::string, std::vector, बड़े ऑब्जेक्ट)।
- कॉलर के वेरिएबल को बदलना है? & (non-const रेफ़रेंस) से पास करें।
- null हो सकता है? पॉइंटर से पास करें। लेकिन इसके बजाय std::optional पर विचार करें।
- ओनरशिप लेनी है (sinking)? वैल्यू से पास करें और move करें (मूव सिमेंटिक्स वाले मॉड्यूल में समझाया गया है)।
मोटे नियम के तौर पर, 2-3 पॉइंटर से बड़े ऑब्जेक्ट (64-बिट पर 16-24 बाइट) को वैल्यू के बजाय रेफ़रेंस से पास किया जाना चाहिए।
- छोटे, सस्ते टाइप वैल्यू से पास करें: int, double, char, bool, string_view
- बड़े केवल-पढ़ने वाले ऑब्जेक्ट const रेफ़रेंस से पास करें: const std::string&, const std::vector
& - non-const रेफ़रेंस से तभी पास करें जब फ़ंक्शन को कॉलर का ऑब्जेक्ट बदलना हो
- जब nullptr एक वैध आर्ग्युमेंट हो तब पॉइंटर से पास करें — लेकिन जहाँ संभव हो std::optional को प्राथमिकता दें
- शक हो तो const रेफ़रेंस से पास करें — यह सुरक्षित और कुशल है
रिटर्न वैल्यू ऑप्टिमाइज़ेशन (RVO/NRVO)
शुरुआती लोगों की एक आम चिंता यह होती है कि बड़े ऑब्जेक्ट को वैल्यू से लौटाना कॉपी के कारण महँगा है। आधुनिक C++ में, Return Value Optimization (RVO) और Named Return Value Optimization (NRVO) की बदौलत यह चिंता लगभग हमेशा निराधार होती है।
RVO (C++17 से गारंटीशुदा): जब कोई फ़ंक्शन कोई टेम्पररी लौटाता है (जैसे return std::vector), तो कंपाइलर को ऑब्जेक्ट सीधे कॉलर की मेमोरी में बनाना ज़रूरी है। कोई कॉपी या मूव नहीं होता।
NRVO (अनुमत, गारंटीशुदा नहीं): जब कोई फ़ंक्शन कोई नामित लोकल वेरिएबल लौटाता है (जैसे return result;), तो कंपाइलर उस वेरिएबल को सीधे कॉलर की मेमोरी में बना सकता है। व्यवहार में प्रमुख कंपाइलर आम तौर पर ऐसा करते हैं, लेकिन इसकी गारंटी नहीं है (यह ऑप्टिमाइज़ेशन सेटिंग और कोड की बनावट पर निर्भर करता है)।
व्यावहारिक सलाह: बेझिझक वैल्यू से लौटाएँ। कॉपी से बचने के लिए आउटपुट पैरामीटर (void f(std::vector) का उपयोग न करें — रिटर्न को कंपाइलर के ऑप्टिमाइज़ करने पर छोड़ दें।
फ़ंक्शन ओवरलोडिंग और डिफ़ॉल्ट आर्ग्युमेंट
C++ एक ही नाम लेकिन अलग पैरामीटर सूची वाले कई फ़ंक्शन की अनुमति देती है। कॉल साइट पर दिए गए आर्ग्युमेंट के आधार पर कंपाइलर सबसे अच्छा मेल चुनता है:
#include <iostream>
#include <string>
#include <vector>
// Function overloading — same name, different parameter types/counts
void log(int value) {
std::cout << "[INT] " << value << '\n';
}
void log(double value) {
std::cout << "[DOUBLE] " << value << '\n';
}
void log(const std::string& value) {
std::cout << "[STRING] " << value << '\n';
}
// Default arguments — provide fallback values
void greet(const std::string& name, const std::string& greeting = "Hello") {
std::cout << greeting << ", " << name << "!\n";
}
// [[nodiscard]] (C++17) — warn if return value is ignored
[[nodiscard]] int compute(int a, int b) {
return a * b + a;
}
// [[nodiscard]] with reason (C++20)
[[nodiscard("Ignoring error codes can hide failures")]]
int open_file(const std::string& path) {
// Returns 0 on success, negative on error
return 0; // Simplified
}
// inline — suggests the compiler inline this function (also allows
// the function to be defined in a header without ODR violations)
inline int fast_max(int a, int b) {
return (a > b) ? a : b;
}
int main() {
log(42); // Calls log(int)
log(3.14); // Calls log(double)
log(std::string{"hi"});// Calls log(const string&)
greet("Alice"); // Uses default: "Hello, Alice!"
greet("Bob", "Good morning"); // Uses provided: "Good morning, Bob!"
// int result = compute(3, 4); // OK
// compute(3, 4); // WARNING: ignoring [[nodiscard]] return value
int status = open_file("data.txt"); // OK: return value used
}इस उदाहरण को कंपाइल करके चलाने के लिए चलाएं दबाएं।
फ़ंक्शन ओवरलोड रिज़ॉल्यूशन जटिल नियमों का पालन करता है, और अस्पष्ट कॉल कंपाइलेशन एरर का एक आम स्रोत हैं। इन जालों से सावधान रहें:
1. इम्प्लिसिट कन्वर्शन अस्पष्टता पैदा करते हैं: अगर आपके पास f(int) और f(double) हैं, तो f(3.14f) (एक float) अस्पष्ट नहीं है — float से int में कन्वर्शन होता है जबकि double में प्रमोशन, इसलिए f(double) चुना जाता है। लेकिन जब कोई आर्ग्युमेंट दो बराबर दर्जे के कन्वर्शन के बीच फँस जाए (जैसे double आर्ग्युमेंट और केवल int व float ओवरलोड), तो कॉल अस्पष्ट हो जाती है। समाधान: सटीक मेल वाला ओवरलोड जोड़ें (जैसे f(float)), या static_cast का उपयोग करें।
2. डिफ़ॉल्ट आर्ग्युमेंट ओवरलोड के साथ ठीक से तालमेल नहीं बिठाते: void f(int x, int y = 0) और void f(int x) एक आर्ग्युमेंट के साथ कॉल करने पर अस्पष्टता पैदा करते हैं।
3. const रेफ़रेंस बनाम वैल्यू: f(int) और f(const int&) lvalue आर्ग्युमेंट के लिए अस्पष्ट हैं, क्योंकि दोनों समान रूप से अच्छे मेल हैं।
जब कंपाइलर अस्पष्ट ओवरलोड रिपोर्ट करे, तो अंदाज़ा न लगाएँ — एरर संदेश ध्यान से पढ़ें। वह बताएगा कि कौन-से ओवरलोड उम्मीदवार हैं और वे समान रूप से अच्छे क्यों हैं।
- इम्प्लिसिट कन्वर्शन ओवरलोड रिज़ॉल्यूशन को अस्पष्ट बना सकते हैं — सुलझाने के लिए स्पष्ट ओवरलोड जोड़ें
- डिफ़ॉल्ट आर्ग्युमेंट और ओवरलोड में टकराव हो सकता है — एक ही फ़ंक्शन नाम पर दोनों से बचें
- शक हो तो कंपाइलर का एरर संदेश उम्मीदवार ओवरलोड और अस्पष्टता की सूची देता है
- जब आर्ग्युमेंट कई ओवरलोड से मेल खाते हों, तो किसी ओवरलोड को स्पष्ट रूप से चुनने के लिए static_cast का उपयोग करें
- छोटे टाइप वैल्यू से, बड़े केवल-पढ़ने वाले टाइप const रेफ़रेंस से, और बदले जाने वाले टाइप non-const रेफ़रेंस से पास करें
- बेझिझक वैल्यू से लौटाएँ — व्यवहार में RVO/NRVO कॉपी को खत्म कर देता है
- फ़ंक्शन ओवरलोडिंग आर्ग्युमेंट टाइप के आधार पर सबसे अच्छा मेल चुनती है — इम्प्लिसिट कन्वर्शन से पैदा होने वाली अस्पष्टता से सावधान रहें
- जिन फ़ंक्शन की रिटर्न वैल्यू को नज़रअंदाज़ नहीं किया जाना चाहिए (एरर कोड, गणना किए गए नतीजे) उन पर [[nodiscard]] का उपयोग करें
- डिफ़ॉल्ट आर्ग्युमेंट सुविधा देते हैं लेकिन ओवरलोड से टकरा सकते हैं — दोनों में से एक ही उपयोग करें
- inline फ़ंक्शन को One Definition Rule का उल्लंघन किए बिना हेडर में परिभाषित करने देता है
क्विज़ — अपना ज्ञान परखें
(15 XP)1. `std::vector<int>` को वैल्यू के बजाय `const&` से कब पास करना चाहिए?
2. Return Value Optimization (RVO) क्या है?
3. `[[nodiscard]]` एट्रिब्यूट क्या करता है?