c++ - पासिंग std:: स्ट्रिंग और पैरामीटर के रूप में पास करने के दिन हैं?




c++11 (10)

मैंने हर्ब सटर द्वारा हाल ही की एक बात सुनी, जिन्होंने सुझाव दिया कि std::vector और std::string को पार करने के कारणों को काफी हद तक चला गया है। उन्होंने सुझाव दिया कि निम्नलिखित कार्यों जैसे लेखन अब बेहतर है:

std::string do_something ( std::string inval )
{
   std::string return_val;
   // ... do stuff ...
   return return_val;
}

मैं समझता हूं कि return_val उस बिंदु पर एक return_val होगी जो फ़ंक्शन लौटाती है और इसलिए return_val अर्थशास्त्र का उपयोग करके वापस लौटाया जा सकता है, जो बहुत सस्ते हैं। हालांकि, inval अभी भी संदर्भ के आकार से काफी बड़ा है (जिसे आमतौर पर सूचक के रूप में लागू किया जाता है)। ऐसा इसलिए है क्योंकि एक std::string में विभिन्न घटक हैं जिनमें ढेर में पॉइंटर और शॉर्ट स्ट्रिंग ऑप्टिमाइज़ेशन के लिए सदस्य char[] । तो मुझे ऐसा लगता है कि संदर्भ से गुज़रना अभी भी एक अच्छा विचार है।

क्या कोई बता सकता है कि हर्ब ने ऐसा क्यों कहा होगा?


पासिंग std :: स्ट्रिंग और पैरामीटर के रूप में पास करने के दिन हैं?

नहीं बहुत से लोग इस सलाह को लेते हैं (डेव अब्राहम सहित) जो डोमेन पर लागू होता है, और सभी std::string पैरामीटर पर लागू करने के लिए इसे सरल बनाते हैं - हमेशा मूल्य से std::string पास करना किसी भी और सभी के लिए "सर्वोत्तम अभ्यास" नहीं है मनमाने ढंग से पैरामीटर और अनुप्रयोगों क्योंकि ऑप्टिमाइज़ेशन इन वार्ता / लेखों पर केंद्रित है केवल मामलों के प्रतिबंधित सेट पर लागू होते हैं

यदि आप कोई मान वापस कर रहे हैं, पैरामीटर को म्यूट कर रहे हैं, या मान ले रहे हैं, तो मूल्य से गुजरना महंगा प्रतिलिपि बचा सकता है और सिंटैक्टिकल सुविधा प्रदान करता है।

जब भी आपको प्रतिलिपि की आवश्यकता नहीं होती है, तब तक, कॉन्स संदर्भ से गुजरने से बहुत प्रतिलिपि होती है

अब विशिष्ट उदाहरण के लिए:

हालांकि आविष्कार अभी भी एक संदर्भ के आकार से काफी बड़ा है (जिसे आमतौर पर सूचक के रूप में लागू किया जाता है)। ऐसा इसलिए है क्योंकि एक std :: स्ट्रिंग में विभिन्न घटक हैं जिनमें ढेर में पॉइंटर और शॉर्ट स्ट्रिंग ऑप्टिमाइज़ेशन के लिए सदस्य char [] शामिल हैं। तो मुझे ऐसा लगता है कि संदर्भ से गुज़रना अभी भी एक अच्छा विचार है। क्या कोई बता सकता है कि हर्ब ने ऐसा क्यों कहा होगा?

यदि ढेर का आकार एक चिंता है (और यह मानते हुए कि यह return_val / अनुकूलित नहीं है), return_val + return_val > return_val - IOW, शीर्ष स्टैक उपयोग को मूल्य से गुजरकर कम किया जा सकता है (नोट: return_val )। इस बीच, कॉन्स्ट संदर्भ द्वारा गुजरने से अनुकूलन अक्षम हो सकते हैं। यहां प्राथमिक कारण स्टैक वृद्धि से बचने के लिए नहीं है, लेकिन यह सुनिश्चित करने के लिए कि अनुकूलन किया जा सकता है, जहां यह लागू हो

कॉन्स संदर्भ से गुजरने के दिन खत्म नहीं हुए हैं - नियम एक बार थे जितना अधिक जटिल थे। यदि प्रदर्शन महत्वपूर्ण है, तो आप अपने कार्यान्वयन में उपयोग किए जाने वाले विवरणों के आधार पर इन प्रकारों को कैसे पारित करते हैं, इस पर विचार करना बुद्धिमान होगा।


कोई रजत बुलेट नहीं है। हमेशा की तरह, यह आपके उपयोग के मामले पर निर्भर करता है।

मेरे मामले में, मैं मूल्य मानकों का उपयोग करता हूं जहां मेरे पास ऐसा फ़ंक्शन होता है जो तथाकथित सिंक तर्क लेता है। एक सिंक तर्क का मूल्य फ़ंक्शन बॉडी में कॉपी किया गया है। आप इस मामले में मूल्य से गुजरते हैं ताकि आप उत्तीर्ण तर्क से निर्माण को स्थानांतरित कर सकें या असाइन कर सकें। यह भी देखें: क्या मुझे हमेशा 'सिंक' कन्स्ट्रक्टर या सेटर तर्क पर जाना चाहिए?

अन्य मामलों में, आप हमेशा ऐसे परिदृश्य के साथ आ सकते हैं जहां एक मान पैरामीटर होने से एक कॉन्स्ट संदर्भ पैरामीटर अधिक कुशल होता है, विशेष रूप से जब फ़ंक्शन का तर्क महंगी प्रति अर्थशास्त्र के साथ एक लवल्यू होता है। एक कॉन्स संदर्भ में एक रैल्यू पास करना कभी भी बुरा नहीं होता है, यह केवल अस्थायी जीवनकाल को बढ़ाता है, जिसे आप सुरक्षित रूप से मान सकते हैं कि कॉन्स्ट संदर्भ फ़ंक्शन कॉल के बाद भी मान्य है, इस मामले में आपको नहीं करना चाहिए संदर्भ की प्रतिलिपि बनाएँ।


मैंने इस प्रश्न से उत्तर को कॉपी / पेस्ट किया है, और इस प्रश्न को फिट करने के लिए नाम और वर्तनी बदल दी है।

पूछा जा रहा है कि मापने के लिए कोड यहां दिया गया है:

#include <iostream>

struct string
{
    string() {}
    string(const string&) {std::cout << "string(const string&)\n";}
    string& operator=(const string&) {std::cout << "string& operator=(const string&)\n";return *this;}
#if (__has_feature(cxx_rvalue_references))
    string(string&&) {std::cout << "string(string&&)\n";}
    string& operator=(string&&) {std::cout << "string& operator=(string&&)\n";return *this;}
#endif

};

#if PROCESS == 1

string
do_something(string inval)
{
    // do stuff
    return inval;
}

#elif PROCESS == 2

string
do_something(const string& inval)
{
    string return_val = inval;
    // do stuff
    return return_val; 
}

#if (__has_feature(cxx_rvalue_references))

string
do_something(string&& inval)
{
    // do stuff
    return std::move(inval);
}

#endif

#endif

string source() {return string();}

int main()
{
    std::cout << "do_something with lvalue:\n\n";
    string x;
    string t = do_something(x);
#if (__has_feature(cxx_rvalue_references))
    std::cout << "\ndo_something with xvalue:\n\n";
    string u = do_something(std::move(x));
#endif
    std::cout << "\ndo_something with prvalue:\n\n";
    string v = do_something(source());
}

मेरे लिए यह आउटपुट:

$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=1 test.cpp
$ a.out
do_something with lvalue:

string(const string&)
string(string&&)

do_something with xvalue:

string(string&&)
string(string&&)

do_something with prvalue:

string(string&&)
$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=2 test.cpp
$ a.out
do_something with lvalue:

string(const string&)

do_something with xvalue:

string(string&&)

do_something with prvalue:

string(string&&)

नीचे दी गई तालिका मेरे परिणामों को सारांशित करती है (clang -std = c ++ 11 का उपयोग करके)। पहला नंबर कॉपी निर्माण की संख्या है और दूसरा नंबर चाल निर्माण की संख्या है:

+----+--------+--------+---------+
|    | lvalue | xvalue | prvalue |
+----+--------+--------+---------+
| p1 |  1/1   |  0/2   |   0/1   |
+----+--------+--------+---------+
| p2 |  1/0   |  0/1   |   0/1   |
+----+--------+--------+---------+

पास-बाय-वैल्यू समाधान के लिए केवल एक ओवरलोड की आवश्यकता होती है लेकिन अंतराल और xvalues ​​गुजरते समय अतिरिक्त कदम निर्माण की लागत होती है। यह किसी भी स्थिति के लिए स्वीकार्य हो सकता है या नहीं भी हो सकता है। दोनों समाधानों के फायदे और नुकसान हैं।


यह अत्यधिक संकलक के कार्यान्वयन पर निर्भर करता है।

हालांकि, यह आपके द्वारा उपयोग किए जाने वाले कार्यों पर भी निर्भर करता है।

आइए अगले कार्यों पर विचार करें:

bool foo1( const std::string v )
{
  return v.empty();
}
bool foo2( const std::string & v )
{
  return v.empty();
}

इन कार्यों को इनलाइनिंग से बचने के लिए एक अलग संकलन इकाई में लागू किया गया है। फिर :
1. यदि आप इन दो कार्यों के लिए एक शाब्दिक पास करते हैं, तो आप प्रदर्शन में बहुत अंतर नहीं देखेंगे। दोनों मामलों में, एक स्ट्रिंग ऑब्जेक्ट बनाना होगा
2. यदि आप एक और std :: स्ट्रिंग ऑब्जेक्ट पास करते हैं, तो foo2 foo1 से बेहतर प्रदर्शन foo1 , क्योंकि foo1 एक गहरी प्रतिलिपि करेगा।

मेरे पीसी पर, जी ++ 4.6.1 का उपयोग करके, मुझे ये परिणाम मिल गए:

  • संदर्भ द्वारा परिवर्तनीय: 1000000000 पुनरावृत्तियों -> समय बीत गया: 2.25912 सेकंड
  • मूल्य से परिवर्तनीय: 1000000000 पुनरावृत्तियों -> समय बीत गया: 27.225 9 सेकंड
  • संदर्भ द्वारा शाब्दिक: 100000000 पुनरावृत्तियों -> समय बीत गया: 9.1031 9 सेकंड
  • मूल्य से शाब्दिक: 100000000 पुनरावृत्तियों -> समय बीत गया: 8.6265 9 सेकंड

संक्षिप्त उत्तर: नहीं! लंबा जवाब:

  • यदि आप स्ट्रिंग को संशोधित नहीं करेंगे (उपचार केवल पढ़ने के लिए है), इसे const ref& रूप में पास करें।
    ( const ref& स्पष्ट रूप से दायरे के भीतर रहने की जरूरत है जबकि फ़ंक्शन जो इसे निष्पादित करता है)
  • यदि आप इसे संशोधित करने की योजना बना रहे हैं या आपको पता है कि यह दायरे (धागे) से बाहर हो जाएगा, इसे एक value रूप में पास करें, const ref& अपने फ़ंक्शन बॉडी के अंदर प्रतिलिपि न लें।

सीपीपी-next.com पर एक पोस्ट थी जिसे "वांछित गति, मूल्य से गुजरना" कहा जाता है ! । टीएल; डीआर:

दिशानिर्देश : अपने फ़ंक्शन तर्कों की प्रतिलिपि न लें। इसके बजाय, उन्हें मूल्य से पास करें और संकलक को कॉपी करने दें।

^ का अनुवाद

अपने फ़ंक्शन तर्कों की प्रतिलिपि न लें --- इसका अर्थ है: यदि आप इसे तर्क मान को किसी आंतरिक चर में कॉपी करके संशोधित करने की योजना बनाते हैं, तो इसके बजाय केवल एक मान तर्क का उपयोग करें

तो, ऐसा मत करो :

std::string function(const std::string& aString){
    auto vString(aString);
    vString.clear();
    return vString;
}

ऐसा करो :

std::string function(std::string aString){
    aString.clear();
    return aString;
}

जब आपको अपने फ़ंक्शन बॉडी में तर्क मान को संशोधित करने की आवश्यकता होती है।

आपको केवल यह पता होना चाहिए कि आप फ़ंक्शन बॉडी में तर्क का उपयोग करने की योजना कैसे बनाते हैं। केवल पढ़ने के लिए या नहीं ... और अगर यह गुंजाइश के भीतर चिपक जाती है।


समस्या यह है कि "कॉन्स" एक गैर-दानेदार क्वालीफायर है। आम तौर पर "कॉन्स स्ट्रिंग रेफ" का अर्थ है "इस स्ट्रिंग को संशोधित न करें", "संदर्भ गणना को संशोधित न करें"। सी ++ में, यह कहने के लिए कि कौन से सदस्य "const" हैं, बस कोई रास्ता नहीं है। वे सभी हैं, या उनमें से कोई भी नहीं हैं।

इस भाषा के मुद्दे के आसपास हैक करने के लिए, एसटीएल आपके उदाहरण में "सी ()" को किसी भी तरह से एक चाल-अर्थपूर्ण प्रतिलिपि बनाने की अनुमति दे सकता है , और संदर्भ गणना के संबंध में "कॉन्स्ट" को कर्तव्यपूर्वक अनदेखा कर सकता है (और इसलिए मानना ​​है कि यह नहीं था घोषित कॉन्स क्योंकि यह mem-mapped या नैनो-थर्ड या जो कुछ भी था)। जब तक यह अच्छी तरह से निर्दिष्ट किया गया था, यह ठीक होगा।

चूंकि एसटीएल नहीं करता है, मेरे पास एक स्ट्रिंग का एक संस्करण है जो const_casts <> संदर्भ काउंटर को दूर करता है, और - लो और देखें - आप स्वतंत्र रूप से cmstring को कॉन्स्ट संदर्भ के रूप में पारित कर सकते हैं, और पूरे दिन गहरे कार्यों में उनकी प्रतियां बना सकते हैं, कोई रिसाव या मुद्दों के साथ।

चूंकि सी ++ यहां कोई कॉन्स ग्रैन्युलरिटी प्रदान नहीं करता है, इसलिए एक अच्छा विनिर्देश लिखना और चमकदार नया "कॉन्स मूवबल स्ट्रिंग" (सेमीस्ट्रिंग) ऑब्जेक्ट बनाना सबसे अच्छा समाधान है जिसे मैंने देखा है।


हर्ब सटर अभी भी रिकॉर्ड पर है, बजेर्न स्ट्राउस्ट्रुप के साथ, const std::string& पैरामीटर प्रकार के रूप में सिफारिश करने में; https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rf-in

यहां किसी भी अन्य उत्तर में कोई गलती नहीं है: यदि आप एक स्ट्रिंग अक्षर को एक const std::string& पैरामीटर में पास करते हैं, तो यह एक अस्थायी स्ट्रिंग का संदर्भ पारित करेगा, जिस पर पात्रों को पकड़ने के लिए ऑन-द-फ्लाई बनाया गया है शाब्दिक यदि आप उस संदर्भ को सहेजते हैं, तो अस्थायी स्ट्रिंग को हटा दिए जाने पर यह अमान्य हो जाएगा। सुरक्षित होने के लिए, आपको एक प्रतिलिपि सहेजनी चाहिए, संदर्भ नहीं। समस्या इस तथ्य से उत्पन्न होती है कि स्ट्रिंग अक्षर const char[N] प्रकार होते हैं, जिन्हें std::string को पदोन्नति की आवश्यकता होती है।

नीचे दिया गया कोड पिटफॉल और वर्कअराउंड को दिखाता है, एक मामूली दक्षता विकल्प के साथ - एक const char* विधि के साथ ओवरलोडिंग, जैसा वर्णन किया गया है कि सी ++ में संदर्भ के रूप में एक स्ट्रिंग अक्षर को पास करने का कोई तरीका है

(नोट: सटर और स्ट्रॉस्ट्रुप सलाह देते हैं कि यदि आप स्ट्रिंग की प्रतिलिपि रखते हैं, तो एक && पैरामीटर और std :: move () के साथ एक ओवरलोडेड फ़ंक्शन भी प्रदान करें।)

#include <string>
#include <iostream>
class WidgetBadRef {
public:
    WidgetBadRef(const std::string& s) : myStrRef(s)  // copy the reference...
    {}

    const std::string& myStrRef;    // might be a reference to a temporary (oops!)
};

class WidgetSafeCopy {
public:
    WidgetSafeCopy(const std::string& s) : myStrCopy(s)
            // constructor for string references; copy the string
    {std::cout << "const std::string& constructor\n";}

    WidgetSafeCopy(const char* cs) : myStrCopy(cs)
            // constructor for string literals (and char arrays);
            // for minor efficiency only;
            // create the std::string directly from the chars
    {std::cout << "const char * constructor\n";}

    const std::string myStrCopy;    // save a copy, not a reference!
};

int main() {
    WidgetBadRef w1("First string");
    WidgetSafeCopy w2("Second string"); // uses the const char* constructor, no temp string
    WidgetSafeCopy w3(w2.myStrCopy);    // uses the String reference constructor
    std::cout << w1.myStrRef << "\n";   // garbage out
    std::cout << w2.myStrCopy << "\n";  // OK
    std::cout << w3.myStrCopy << "\n";  // OK
}

उत्पादन:

const char * constructor
const std::string& constructor

Second string
Second string

मूलभूत बातों पर "हर्ब सटर" देखें ! आधुनिक सी ++ स्टाइल की अनिवार्यताएं । अन्य विषयों के अलावा, वह अतीत में दी गई सलाह देने वाले पैरामीटर की समीक्षा करता है, और नए विचार जो सी ++ 11 के साथ आते हैं और विशेष रूप से देखते हैं मूल्य से तार पारित करने का विचार।

बेंचमार्क दिखाते हैं कि std::string s को मूल्य से गुजरना, ऐसे मामलों में जहां फ़ंक्शन इसे किसी भी तरह कॉपी करेगा, काफी धीमा हो सकता है!

ऐसा इसलिए है क्योंकि आप हमेशा एक पूर्ण प्रतिलिपि बनाने के लिए मजबूर कर रहे हैं (और फिर स्थानांतरित हो जाते हैं), जबकि const& संस्करण पुरानी स्ट्रिंग को अपडेट करेगा जो पहले से आवंटित बफर का पुन: उपयोग कर सकता है।

उसकी स्लाइड देखें 27: "सेट" फ़ंक्शंस के लिए, विकल्प 1 वही है जैसा हमेशा होता था। विकल्प 2 रावल्यू संदर्भ के लिए एक अधिभार जोड़ता है, लेकिन यदि एकाधिक पैरामीटर हैं तो यह एक संयोजन विस्फोट देता है।

यह केवल "सिंक" पैरामीटर के लिए है जहां एक स्ट्रिंग बनाई जानी चाहिए (इसका मौजूदा मान नहीं बदला गया है) कि पास-बाय-वैल्यू चाल वैध है। यही वह रचनाकार है जिसमें पैरामीटर सीधे मिलान प्रकार के सदस्य को प्रारंभ करता है।

यदि आप देखना चाहते हैं कि आप इस बारे में चिंता करने में कितना गहराई से जा सकते हैं, तो निकोलई जोसुटिस की प्रेजेंटेशन और उस के साथ शुभकामनाएं देखें (पिछले संस्करण के साथ गलती खोजने के बाद "बिल्कुल सही - हो गया!" कभी भी?)


std::string लिए C ++ संदर्भ का उपयोग कर आईएमओ एक त्वरित और छोटा स्थानीय अनुकूलन है, जबकि मूल्य से गुजरने का उपयोग बेहतर वैश्विक अनुकूलन (या नहीं) हो सकता है।

तो जवाब है: यह परिस्थितियों पर निर्भर करता है:

  1. यदि आप बाहरी कोड से बाहर के सभी कोड लिखते हैं, तो आप जानते हैं कि कोड क्या करता है, आप संदर्भ const std::string & उपयोग कर सकते हैं।
  2. यदि आप लाइब्रेरी कोड लिखते हैं या स्ट्रिंग्स पास होने पर भारी लाइब्रेरी कोड का उपयोग करते हैं, तो संभवतः आप std::string कॉपी कन्स्ट्रक्टर व्यवहार पर भरोसा करके वैश्विक अर्थ में अधिक लाभ प्राप्त कर सकते हैं।

नहीं

क्यों नहीं? क्या होगा यदि आपके पास 10 ^ 256 पूर्णांक के वेक्टर हैं जो अनुसंधान संस्थान को बदलने की जरूरत नहीं है? क्या होगा यदि आप उस वेक्टर को प्रतिलिपि बनाने के लिए हर बार कॉपी करते हैं? मैं @ निकोलबोलस के उत्तर से सहमत हूं, लेकिन मैं श्री हर्ब सटर से सहमत नहीं हूं।

उदाहरण के लिए सीईआरएन प्रोजेक्ट लें, उन्होंने एक सेकंड में बहुत से डेटा एकत्र किए, मेरे लिए निश्चित रूप से सभी लोगों को मूल डेटा के साथ गड़बड़ करना अच्छा नहीं होगा।





c++11