c++ - पास-दर-मूल्य के लिए पसंदीदा संदर्भ(एल-वैल्यू और आर-वैल्यू) द्वारा ओवरलोडिंग पास कब होता है?



c++11 overloading (1)

उन प्रकारों के लिए जिनकी कॉपी असाइनमेंट ऑपरेटर संसाधनों को रीसायकल कर सकता है, प्रतिलिपि के साथ स्वैपिंग प्रतिलिपि असाइनमेंट ऑपरेटर को लागू करने का सबसे अच्छा तरीका नहीं है। उदाहरण के लिए std::vector :

यह वर्ग एक गतिशील आकार के बफर का प्रबंधन करता है और capacity (अधिकतम लंबाई बफर पकड़ सकता है), और एक size (वर्तमान लंबाई) दोनों को बनाए रखता है। यदि vector कॉपी असाइनमेंट ऑपरेटर को swap लागू किया गया है, तो कोई फर्क नहीं पड़ता कि rhs.size() != 0 अगर हमेशा एक नया बफर आवंटित किया जाता है।

हालांकि, अगर lhs.capacity() >= rhs.size() , कोई नया बफर आवंटित करने की आवश्यकता नहीं है। कोई भी बस rhs से lhs के तत्वों को असाइन / निर्माण कर lhs । जब तत्व का प्रकार तुच्छ रूप से कॉपी करने योग्य होता है, तो यह memcpy कुछ भी उबाल सकता है। यह बफर को आवंटित करने और हटाने से कहीं अधिक तेज़ हो सकता है।

std::string लिए एक ही समस्या।

MyType लिए वही समस्या जब MyType में डेटा सदस्य हैं जो std::vector और / या std::string

स्वैप के साथ कॉपी असाइनमेंट को लागू करने पर विचार करने के लिए केवल 2 बार हैं:

  1. आप जानते हैं कि swap विधि (अनिवार्य प्रति निर्माण सहित जब rhs एक lvalue है) बहुत अक्षम नहीं होगा।

  2. आप जानते हैं कि मजबूत अपवाद सुरक्षा गारंटी रखने के लिए आपको हमेशा कॉपी असाइनमेंट ऑपरेटर की आवश्यकता होगी।

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

  1. एक अस्वीकरण स्वैप।
  2. एक अस्वीकरण चाल असाइनमेंट ऑपरेटर।

उदाहरण के लिए:

template <class T>
T&
strong_assign(T& x, T y)
{
    using std::swap;
    swap(x, y);
    return x;
}

या:

template <class T>
T&
strong_assign(T& x, T y)
{
    x = std::move(y);
    return x;
}

अब कुछ प्रकार होंगे जहां स्वैप के साथ कॉपी असाइनमेंट लागू करना समझ में आएगा। हालांकि इन प्रकारों का अपवाद होगा, नियम नहीं।

पर:

void push_back(const value_type& val);
void push_back(value_type&& val);

vector<big_legacy_type> कल्पना करें जहां:

class big_legacy_type
{
 public:
      big_legacy_type(const big_legacy_type&);  // expensive
      // no move members ...
};

अगर हमारे पास केवल था:

void push_back(value_type val);

फिर एक vector में एक lvalue big_legacy_type को push_back में 1 की बजाय 2 प्रतियों की आवश्यकता होगी, भले ही capacity पर्याप्त थी। यह एक आपदा, प्रदर्शन के अनुसार होगा।

अद्यतन करें

यहां एक हैलोवर्ल्ड है कि आप किसी भी सी ++ 11 अनुरूप प्लेटफ़ॉर्म पर चलाने में सक्षम होना चाहिए:

#include <vector>
#include <random>
#include <chrono>
#include <iostream>

class X
{
    std::vector<int> v_;
public:
    explicit X(unsigned s) : v_(s) {}

#if SLOW_DOWN
    X(const X&) = default;
    X(X&&) = default;
    X& operator=(X x)
    {
        v_.swap(x.v_);
        return *this;
    }
#endif
};

std::mt19937_64 eng;
std::uniform_int_distribution<unsigned> size(0, 1000);

std::chrono::high_resolution_clock::duration
test(X& x, const X& y)
{
    auto t0 = std::chrono::high_resolution_clock::now();
    x = y;
    auto t1 = std::chrono::high_resolution_clock::now();
    return t1-t0;
}

int
main()
{
    const int N = 1000000;
    typedef std::chrono::duration<double, std::nano> nano;
    nano ns(0);
    for (int i = 0; i < N; ++i)
    {
        X x1(size(eng));
        X x2(size(eng));
        ns += test(x1, x2);
    }
    ns /= N;
    std::cout << ns.count() << "ns\n";
}

मैंने X की कॉपी असाइनमेंट ऑपरेटर को दो तरीकों से कोड किया है:

  1. स्पष्ट रूप से, जो vector की कॉपी असाइनमेंट ऑपरेटर को कॉल करने के बराबर है।
  2. कॉपी / स्वैप मुहावरे के साथ, मैक्रो SLOW_DOWN तहत सुझाव दिया गया है। मैंने इसे SLEEP_FOR_AWHILE नाम देने के बारे में सोचा, लेकिन अगर आप बैटरी संचालित डिवाइस पर हैं तो वास्तव में नींद के बयान से भी बदतर है।

परीक्षण कुछ यादृच्छिक रूप से आकार वाले vector<int> एस को 0 और 1000 के बीच बनाता है, और उन्हें दस लाख बार असाइन करता है। यह हर बार होता है, समय बताता है, और फिर फ्लोटिंग पॉइंट नैनोसेकंड में औसत समय पाता है और प्रिंट करता है। यदि आपकी उच्च रिज़ॉल्यूशन घड़ी में लगातार दो कॉल 100 नैनोसेकंड से कम कुछ नहीं लौटाती हैं, तो आप वैक्टर की लंबाई बढ़ा सकते हैं।

मेरे परिणाम यहां दिए गए हैं:

$ clang++ -std=c++11 -stdlib=libc++ -O3 test.cpp
$ a.out
428.348ns
$ a.out
438.5ns
$ a.out
431.465ns
$ clang++ -std=c++11 -stdlib=libc++ -O3 -DSLOW_DOWN test.cpp
$ a.out
617.045ns
$ a.out
616.964ns
$ a.out
618.808ns

मैं इस सरल परीक्षण के साथ कॉपी / स्वैप मुहावरे के लिए 43% प्रदर्शन हिट देख रहा हूं। YMMV।

उपरोक्त परीक्षण, औसतन, आधा समय lhs पर पर्याप्त क्षमता है। अगर हम इसे चरम पर लेते हैं:

  1. एलएचएस में हर समय पर्याप्त क्षमता होती है।
  2. एलएचएस में पर्याप्त समय नहीं है।

तो प्रतिलिपि / स्वैप मुहावरे पर डिफ़ॉल्ट प्रति असाइनमेंट का प्रदर्शन लाभ लगभग 560% से 0% तक भिन्न होता है। प्रतिलिपि / स्वैप मुहावरे कभी तेज़ नहीं होता है, और नाटकीय रूप से धीमा हो सकता है (इस परीक्षण के लिए)।

गति चाहते हैं? का आकलन करें।

मैंने देखा है कि यह कहा गया है कि एक operator= उसी प्रकार के मानदंड का पैरामीटर लेने के लिए लिखा गया है, दोनों कॉपी असाइनमेंट ऑपरेटर के रूप में कार्य करता है और सी ++ 11 में असाइनमेंट ऑपरेटर ले जाता है:

Foo& operator=(Foo f)
{
    swap(f);
    return *this;
}

जहां विकल्प कोड कोड पुनरावृत्ति के साथ कई बार लाइनों से अधिक होगा, और त्रुटि के लिए संभावित:

Foo& operator=(const Foo& f)
{
    Foo f2(f);
    swap(f2);
    return *this;
}

Foo& operator=(Foo&& f)
{
    Foo f2(std::move(f));
    swap(f2);
    return *this;
}

किस परिस्थिति में मूल्य से गुजरने के लिए रीफ-टू-कॉन्स और आर-वैल्यू ओवरलोड बेहतर है, या यह कब आवश्यक है? मैं std::vector::push_back बारे में सोच रहा हूं, उदाहरण के लिए जिसे दो ओवरलोड के रूप में परिभाषित किया गया है:

void push_back (const value_type& val);
void push_back (value_type&& val);

पहले उदाहरण के बाद जहां मूल्य द्वारा पास कॉपी असाइनमेंट ऑपरेटर के रूप में कार्य करता है और असाइनमेंट ऑपरेटर ले जाता है , क्या मानक में एक ही फ़ंक्शन होने के लिए push_back को परिभाषित नहीं किया जा सकता है?

void push_back (value_type val);




copy-and-swap