initializer_list और शब्दार्थ को स्थानांतरित करें


97

क्या मुझे तत्वों को एक से बाहर जाने की अनुमति है std::initializer_list<T>?

#include <initializer_list>
#include <utility>

template<typename T>
void foo(std::initializer_list<T> list)
{
    for (auto it = list.begin(); it != list.end(); ++it)
    {
        bar(std::move(*it));   // kosher?
    }
}

चूँकि std::intializer_list<T>विशेष संकलक ध्यान देने की आवश्यकता होती है और इसमें C ++ मानक पुस्तकालय के सामान्य कंटेनरों की तरह मूल्य शब्दार्थ नहीं होते हैं, इसलिए मैं खेद और पूछने के बजाय सुरक्षित रहूंगा।


कोर भाषा को परिभाषित करता है कि वस्तु एक से जाना जाता initializer_list<T>हैं गैर -const। जैसे, वस्तुओं initializer_list<int>को संदर्भित करता है int। लेकिन मुझे लगता है कि यह एक दोष है - यह इरादा है कि संकलक केवल स्मृति में पढ़ने के लिए एक सूची आवंटित कर सकते हैं।
जोहान्स स्काउब -

जवाबों:


89

नहीं, यह इरादा के अनुसार काम नहीं करेगा; आपको अभी भी प्रतियां मिलेंगी। मुझे इस बात से बहुत आश्चर्य हुआ, क्योंकि मैंने सोचा था कि initializer_listजब तक वे move'डी' नहीं थे, तब तक अस्थायी लोगों की एक सरणी रखने के लिए मौजूद थे ।

beginऔर endके लिए initializer_listवापसी const T *, इसलिए का परिणाम moveअपने कोड में है T const &&- एक अपरिवर्तनीय rvalue संदर्भ। इस तरह की अभिव्यक्ति को सार्थक रूप से स्थानांतरित नहीं किया जा सकता है। यह एक प्रकार के फंक्शन पैरामीटर से T const &बंधेगा क्योंकि rvalues ​​कॉन्स्टेंट लैवल्यू रेफरेंस को बाइंड करते हैं, और आप अभी भी कॉपी सिमेंटिक्स देखेंगे।

शायद इसका कारण तो संकलक बनाने के लिए चुनाव कर सकते हैं है initializer_listएक स्थिर-initialized निरंतर, लेकिन ऐसा लगता है कि यह क्लीनर होगा अपनी तरह बनाने के लिए initializer_listया const initializer_list, संकलक के विवेक पर तो उपयोगकर्ता एक उम्मीद है कि क्या पता नहीं है constया परिवर्तनशील से परिणाम beginऔर end। लेकिन यह सिर्फ मेरी आंत की भावना है, शायद एक अच्छा कारण है जो मैं गलत हूं।

अपडेट: मैंने केवल चाल के प्रकारों के समर्थन के लिए एक आईएसओ प्रस्ताव लिखा है initializer_list। यह केवल एक पहला मसौदा है, और इसे अभी तक कहीं भी लागू नहीं किया गया है, लेकिन आप इसे समस्या के अधिक विश्लेषण के लिए देख सकते हैं।


11
यदि यह स्पष्ट नहीं है, तो इसका मतलब है कि इसका उपयोग std::moveसुरक्षित है, यदि यह उत्पादक नहीं है। (बैरिंग T const&&मूव कन्स्ट्रक्टर्स।)
ल्यूक डैंटन

मुझे नहीं लगता कि आप पूरे तर्क या तो बना सकता है const std::initializer_list<T>बस या std::initializer_list<T>एक तरीका है कि आश्चर्य कारण अक्सर नहीं होता है में। विचार करें कि प्रत्येक तर्क या initializer_listतो हो सकता है constया नहीं और यह कॉलर के संदर्भ में जाना जाता है, लेकिन संकलक को कोडली के संदर्भ में कोड का सिर्फ एक संस्करण उत्पन्न करना चाहिए (अर्थात इसके अंदर fooतर्कों के बारे में कुछ भी नहीं पता है) कि कॉलर गुजर रहा है)
डेविड रोड्रिगेज -

1
@ डेविड: अच्छी बात है, लेकिन यह अभी भी उपयोगी होगा कि std::initializer_list &&ओवरलोड होने पर कुछ करना चाहिए, भले ही एक गैर-संदर्भ ओवरलोड भी आवश्यक हो। मुझे लगता है कि यह मौजूदा स्थिति की तुलना में कहीं अधिक भ्रामक होगा, जो पहले से ही खराब है।
पोटाटोस्वाटर

1
@JBJansen इसे हैक नहीं किया जा सकता है। मैं यह नहीं देखता कि wrt initializer_list को पूरा करने के लिए उस कोड को क्या माना जाता है, लेकिन उपयोगकर्ता के रूप में आपके पास इसे स्थानांतरित करने के लिए आवश्यक अनुमतियाँ नहीं हैं। सुरक्षित कोड ऐसा नहीं करेगा।
पोटाटोस्वाटर

1
@Potatoswatter, देर से टिप्पणी, लेकिन प्रस्ताव की स्थिति क्या है। क्या कोई दूरस्थ मौका है जो इसे C ++ 20 में बना सकता है?
WhiZTiM

20
bar(std::move(*it));   // kosher?

इस तरह से नहीं कि आप इरादा रखते हैं। आप किसी constऑब्जेक्ट को स्थानांतरित नहीं कर सकते । और std::initializer_listकेवल constअपने तत्वों तक पहुंच प्रदान करता है । तो का प्रकार itहै const T *

कॉल करने के आपके प्रयास का std::move(*it)परिणाम केवल एल-मान होगा। IE: एक प्रति।

std::initializer_listस्थिर स्मृति का संदर्भ देता है । क्लास के लिए यही है। आप नहीं कर सकते हैं के लिए कदम स्थिर स्मृति से, क्योंकि आंदोलन को इसमें बदलाव करने का तात्पर्य। आप केवल इससे कॉपी कर सकते हैं।


एक const xvalue अभी भी एक xvalue है, और initializer_listयदि आवश्यक हो तो स्टैक का संदर्भ देता है। (यदि सामग्री स्थिर नहीं है, तो यह अभी भी थ्रेड-सुरक्षित है।)
पोटाटोस्वाटर

5
@Potatoswatter: आप किसी स्थिर ऑब्जेक्ट से नहीं जा सकते। initializer_listवस्तु अपने आप में एक XValue हो सकता है, लेकिन यह सामग्री (वास्तविक मूल्यों की है कि यह की ओर इशारा करता सरणी) हैं const, क्योंकि उन सामग्री स्थिर मान हो सकता है। आप बस की सामग्री से स्थानांतरित नहीं कर सकते initializer_list
निकोल बोलस

देखिए मेरा जवाब और इसकी चर्चा। उन्होंने एक constxvalue का निर्माण करते हुए, डिफरेंस किए गए पुनरावृत्ति को स्थानांतरित कर दिया है । moveनिरर्थक हो सकता है, लेकिन यह कानूनी है और एक पैरामीटर घोषित करना भी संभव है जो बस स्वीकार करता है। यदि किसी विशेष प्रकार का घूमना नो-ओप होता है, तो यह सही तरीके से काम भी कर सकता है।
पोटाटोस्वाटर

1
@Potatoswatter: C ++ 11 मानक बहुत सी भाषा का विस्तार करता है यह सुनिश्चित करता है कि जब तक आप उपयोग नहीं करते हैं तब तक गैर-अस्थायी ऑब्जेक्ट वास्तव में स्थानांतरित नहीं होते हैं std::move। यह सुनिश्चित करता है कि आप निरीक्षण से बता सकते हैं कि एक चालन ऑपरेशन कब होता है, क्योंकि यह स्रोत और गंतव्य दोनों को प्रभावित करता है (आप नहीं चाहते कि इसका नाम वस्तुओं के लिए निहित रूप से हो)। उसके कारण, यदि आप std::moveकिसी ऐसे स्थान पर उपयोग करते हैं , जहां एक चालन ऑपरेशन नहीं होता है (और कोई वास्तविक गति नहीं होगी यदि आपके पास कोई constxvalue है), तो कोड भ्रामक है। मुझे लगता है कि std::moveकिसी constवस्तु पर कॉल करने योग्य होना एक गलती है ।
निकोल बोलस

1
हो सकता है, लेकिन मैं अभी भी भ्रामक कोड की संभावना को लेकर नियमों को कम अपवाद मानूंगा। वैसे भी, यही कारण है कि मैंने "नहीं" का जवाब दिया, भले ही यह कानूनी हो, और इसका परिणाम एक ऐसा परिणाम है, भले ही यह केवल एक कॉन्स्टेबल अंतराल के रूप में बंधेगा। ईमानदार होने के लिए, मैंने पहले से ही const &&प्रबंधित बिंदुओं के साथ कचरा-एकत्र वर्ग में एक संक्षिप्त छेड़खानी की है , जहां सब कुछ प्रासंगिक था और हिलाने से सूचक प्रबंधन चला गया लेकिन निहित मूल्य को प्रभावित नहीं किया। हमेशा ट्रिकी एज केस होते हैं: v)।
पोटैटोसवाटर

2

यह कहा गया काम नहीं करेगा, क्योंकि list.begin()प्रकार है const T *, और कोई रास्ता नहीं है जिससे आप एक स्थिर ऑब्जेक्ट से स्थानांतरित कर सकते हैं। भाषा डिजाइनरों ने संभवत: ऐसा किया ताकि इनिशियल लिस्ट को उदाहरण के लिए स्ट्रिंग स्थिरांक को शामिल करने की अनुमति दी जा सके, जिससे इसे स्थानांतरित करना अनुचित होगा।

हालाँकि, यदि आप ऐसी स्थिति में हैं, जहाँ आप जानते हैं कि इनिशियल लिस्ट में व्याप्त भाव हैं (या आप उपयोगकर्ता को लिखने के लिए बाध्य करना चाहते हैं) तो एक ट्रिक है जिससे यह काम करेगा (मैं सुमंत के उत्तर से प्रेरित था) यह, लेकिन समाधान उस की तुलना में सरल है)। आपको प्रारंभिक सूची में संग्रहीत तत्वों को Tमानों की नहीं , बल्कि मूल्यों की आवश्यकता होती है जो एनकैप्सुलेट करते हैं T&&। फिर भले ही वे मूल्य स्वयं constयोग्य हों, फिर भी वे एक परिवर्तनशील परिवर्तन को पुनः प्राप्त कर सकते हैं।

template<typename T>
  class rref_capture
{
  T* ptr;
public:
  rref_capture(T&& x) : ptr(&x) {}
  operator T&& () const { return std::move(*ptr); } // restitute rvalue ref
};

अब initializer_list<T>आप एक तर्क की घोषणा करने के बजाय एक तर्क की घोषणा करते initializer_list<rref_capture<T> >हैं। यहां एक ठोस उदाहरण है, जिसमें std::unique_ptr<int>स्मार्ट पॉइंटर्स का एक वेक्टर शामिल है , जिसके लिए केवल मूवमेंट को परिभाषित किया गया है (इसलिए ये ऑब्जेक्ट्स को इनिशियलाइज़र लिस्ट में कभी स्टोर नहीं किया जा सकता है); फिर भी समस्या के बिना संकलनकर्ता सूची नीचे दी गई है।

#include <memory>
#include <initializer_list>
class uptr_vec
{
  typedef std::unique_ptr<int> uptr; // move only type
  std::vector<uptr> data;
public:
  uptr_vec(uptr_vec&& v) : data(std::move(v.data)) {}
  uptr_vec(std::initializer_list<rref_capture<uptr> > l)
    : data(l.begin(),l.end())
  {}
  uptr_vec& operator=(const uptr_vec&) = delete;
  int operator[] (size_t index) const { return *data[index]; }
};

int main()
{
  std::unique_ptr<int> a(new int(3)), b(new int(1)),c(new int(4));
  uptr_vec v { std::move(a), std::move(b), std::move(c) };
  std::cout << v[0] << "," << v[1] << "," << v[2] << std::endl;
}

एक प्रश्न के उत्तर की आवश्यकता होती है: यदि इनिशियलाइज़र सूची के तत्वों को सही मूल्य (उदाहरण में वे xvalues ​​हैं) होना चाहिए, तो क्या भाषा यह सुनिश्चित करती है कि संबंधित अस्थायी लोगों का जीवनकाल उस बिंदु तक फैले जहां वे उपयोग किए जाते हैं? सच कहूं, तो मुझे नहीं लगता कि मानक खंड 8.5 के प्रासंगिक खंड इस मुद्दे को बिल्कुल भी संबोधित करते हैं। हालाँकि, 1.9: 10 को पढ़ते हुए, ऐसा लगता है कि सभी मामलों में प्रासंगिक पूर्ण-अभिव्यक्ति आरंभीकरण सूची के उपयोग को समाहित करती है, इसलिए मुझे लगता है कि प्रतिद्वंद्वियों के संदर्भ को खतरे में डालने का कोई खतरा नहीं है।


स्ट्रिंग स्थिरांक? पसंद है "Hello world"? यदि आप उनसे आगे बढ़ते हैं, तो आप केवल एक पॉइंटर को कॉपी करते हैं (या एक संदर्भ को बांधते हैं)।
जिप

1
"एक प्रश्न के उत्तर की आवश्यकता है" अंदर के initializers {..}फ़ंक्शन पैरामीटर में संदर्भों के लिए बाध्य हैं rref_capture। यह उनके जीवनकाल का विस्तार नहीं करता है, वे अभी भी पूर्ण अभिव्यक्ति के अंत में नष्ट हो गए हैं जिसमें वे बनाए गए हैं।
dyp

प्रति टीसी के एक और उत्तर से टिप्पणी: यदि आप निर्माता के कई भार के है, तो लपेट std::initializer_list<rref_capture<T>>- मान लें, अपनी पसंद के कुछ परिवर्तन विशेषता में std::decay_t- अवांछित कटौती ब्लॉक करने के लिए।
मोनिका

2

मुझे लगा कि वर्कअराउंड के लिए उचित शुरुआती बिंदु पेश करना शिक्षाप्रद हो सकता है।

टिप्पणियाँ इनलाइन।

#include <memory>
#include <vector>
#include <array>
#include <type_traits>
#include <algorithm>
#include <iterator>

template<class Array> struct maker;

// a maker which makes a std::vector
template<class T, class A>
struct maker<std::vector<T, A>>
{
  using result_type = std::vector<T, A>;

  template<class...Ts>
  auto operator()(Ts&&...ts) const -> result_type
  {
    result_type result;
    result.reserve(sizeof...(Ts));
    using expand = int[];
    void(expand {
      0,
      (result.push_back(std::forward<Ts>(ts)),0)...
    });

    return result;
  }
};

// a maker which makes std::array
template<class T, std::size_t N>
struct maker<std::array<T, N>>
{
  using result_type = std::array<T, N>;

  template<class...Ts>
  auto operator()(Ts&&...ts) const
  {
    return result_type { std::forward<Ts>(ts)... };
  }

};

//
// delegation function which selects the correct maker
//
template<class Array, class...Ts>
auto make(Ts&&...ts)
{
  auto m = maker<Array>();
  return m(std::forward<Ts>(ts)...);
}

// vectors and arrays of non-copyable types
using vt = std::vector<std::unique_ptr<int>>;
using at = std::array<std::unique_ptr<int>,2>;


int main(){
    // build an array, using make<> for consistency
    auto a = make<at>(std::make_unique<int>(10), std::make_unique<int>(20));

    // build a vector, using make<> because an initializer_list requires a copyable type  
    auto v = make<vt>(std::make_unique<int>(10), std::make_unique<int>(20));
}

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

1
@underscore_d आप बिलकुल सही हैं। मैं यह विचार करता हूं कि प्रश्न से संबंधित ज्ञान को साझा करना अपने आप में एक अच्छी बात है। इस मामले में, शायद इससे ओपी को मदद मिली और शायद ऐसा नहीं हुआ - उसने कोई जवाब नहीं दिया। हालांकि, अक्सर नहीं, ओपी और अन्य लोग प्रश्न से संबंधित अतिरिक्त सामग्री का स्वागत करते हैं।
रिचर्ड होजेस

निश्चित रूप से, यह वास्तव में उन पाठकों के लिए मदद कर सकता है जो कुछ चाहते हैं, initializer_listलेकिन उन सभी बाधाओं के अधीन नहीं हैं जो इसे उपयोगी बनाते हैं। :)
अंडरस्कोर_ड

@underscore_d मैंने किन बाधाओं को अनदेखा किया है?
रिचर्ड होजेस ने

मेरा तात्पर्य यह है कि initializer_list(संकलक जादू के माध्यम से) तत्वों की संख्या पर टेम्प्लेट फ़ंक्शंस होने से बचा जाता है, कुछ ऐसा जो एरे और / या वेरिएडिक फ़ंक्शंस के आधार पर वैकल्पिक रूप से आवश्यक होता है, इस प्रकार उन मामलों की सीमा को बाधित करता है जहां बाद वाले उपयोग करने योग्य होते हैं। मेरी समझ से, यह होने के लिए मुख्य तर्क में से एक है initializer_list, इसलिए यह ध्यान देने योग्य था।
अंडरस्कोर_ड

0

ऐसा लगता है कि मौजूदा मानक में पहले से ही उत्तर की अनुमति नहीं है । फ़ंक्शन को वैरिएडिक सूची में लेने के बजाय वेरिएडिक के रूप में परिभाषित करके, कुछ समान हासिल करने के लिए यहां एक और समाधान है।

#include <vector>
#include <utility>

// begin helper functions

template <typename T>
void add_to_vector(std::vector<T>* vec) {}

template <typename T, typename... Args>
void add_to_vector(std::vector<T>* vec, T&& car, Args&&... cdr) {
  vec->push_back(std::forward<T>(car));
  add_to_vector(vec, std::forward<Args>(cdr)...);
}

template <typename T, typename... Args>
std::vector<T> make_vector(Args&&... args) {
  std::vector<T> result;
  add_to_vector(&result, std::forward<Args>(args)...);
  return result;
}

// end helper functions

struct S {
  S(int) {}
  S(S&&) {}
};

void bar(S&& s) {}

template <typename T, typename... Args>
void foo(Args&&... args) {
  std::vector<T> args_vec = make_vector<T>(std::forward<Args>(args)...);
  for (auto& arg : args_vec) {
    bar(std::move(arg));
  }
}

int main() {
  foo<S>(S(1), S(2), S(3));
  return 0;
}

वैरिएडिक टेम्प्लेट आर-वैल्यू रेफरेंस को इनिशियलाइज़र_लिस्ट के विपरीत उचित तरीके से हैंडल कर सकते हैं।

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


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

0

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

आवरण वर्ग का उपयोग करने के लिए डिज़ाइन किया गया है जिस तरह std::moveसे उपयोग किया जाता है, बस के std::moveसाथ प्रतिस्थापित किया जाता है move_wrapper, लेकिन इसके लिए C ++ 17 की आवश्यकता होती है। पुराने चश्मे के लिए, आप एक अतिरिक्त बिल्डर विधि का उपयोग कर सकते हैं।

आपको बिल्डर तरीकों / रचनाकारों को लिखना होगा जो अंदर रैपर कक्षाओं को स्वीकार करते हैं initializer_listऔर तदनुसार तत्वों को स्थानांतरित करते हैं।

यदि आपको स्थानांतरित होने के बजाय कुछ तत्वों की प्रतिलिपि बनाने की आवश्यकता है, तो इसे पास करने से पहले एक प्रतिलिपि बनाएँ initializer_list

कोड स्व-दस्तावेज होना चाहिए।

#include <iostream>
#include <vector>
#include <initializer_list>

using namespace std;

template <typename T>
struct move_wrapper {
    T && t;

    move_wrapper(T && t) : t(move(t)) { // since it's just a wrapper for rvalues
    }

    explicit move_wrapper(T & t) : t(move(t)) { // acts as std::move
    }
};

struct Foo {
    int x;

    Foo(int x) : x(x) {
        cout << "Foo(" << x << ")\n";
    }

    Foo(Foo const & other) : x(other.x) {
        cout << "copy Foo(" << x << ")\n";
    }

    Foo(Foo && other) : x(other.x) {
        cout << "move Foo(" << x << ")\n";
    }
};

template <typename T>
struct Vec {
    vector<T> v;

    Vec(initializer_list<T> il) : v(il) {
    }

    Vec(initializer_list<move_wrapper<T>> il) {
        v.reserve(il.size());
        for (move_wrapper<T> const & w : il) {
            v.emplace_back(move(w.t));
        }
    }
};

int main() {
    Foo x{1}; // Foo(1)
    Foo y{2}; // Foo(2)

    Vec<Foo> v{Foo{3}, move_wrapper(x), Foo{y}}; // I want y to be copied
    // Foo(3)
    // copy Foo(2)
    // move Foo(3)
    // move Foo(1)
    // move Foo(2)
}

0

ए का उपयोग करने के बजाय std::initializer_list<T>, आप अपने तर्क को एक सरणी संदर्भ संदर्भ के रूप में घोषित कर सकते हैं:

template <typename T>
void bar(T &&value);

template <typename T, size_t N>
void foo(T (&&list)[N] ) {
   std::for_each(std::make_move_iterator(std::begin(list)),
                 std::make_move_iterator(std::end(list)),
                 &bar);
}

void baz() {
   foo({std::make_unique<int>(0), std::make_unique<int>(1)});
}

उदाहरण देखें std::unique_ptr<int>: https://gcc.godbolt.org/z/2uNxv6


-1

Cpptruthsin<T> पर वर्णित मुहावरे पर विचार करें । विचार रन-टाइम पर lvalue / rvalue निर्धारित करना है और फिर कॉल मूव या कॉपी-कंस्ट्रक्शन है। भले ही इनिशियलाइज़र_लिस्ट द्वारा प्रदान किया गया मानक इंटरफ़ेस कॉन्स्ट रेफरेंस है, फिर भी रेवल्यू / लैवल्यू का पता लगाएगा।in<T>


4
जब संकलक पहले से ही जानता है, तो आप रनटाइम पर मूल्य श्रेणी क्यों निर्धारित करना चाहेंगे?
fredoverflow

1
यदि आप असहमत हैं या बेहतर विकल्प हैं, तो कृपया ब्लॉग पढ़ें और मुझे एक टिप्पणी छोड़ दें। यहां तक ​​कि अगर कंपाइलर को मूल्य श्रेणी पता है, तो initializer_list इसे संरक्षित नहीं करता है क्योंकि इसमें केवल कांस्ट पुनरावृत्त हैं। इसलिए जब आप इनिशलाइज़र_लिस्ट का निर्माण करते हैं तो आपको मूल्य श्रेणी को "कैप्चर" करने की आवश्यकता होती है और इसे पास किया जाता है ताकि फ़ंक्शन इसका उपयोग कर सके जैसा कि यह प्रसन्न करता है।
सुमंत

5
यह उत्तर मूल रूप से लिंक का पालन किए बिना बेकार है, और एसओ उत्तर लिंक का पालन किए बिना उपयोगी होना चाहिए।
यक्क - एडम नेवरामॉन्ट

1
@ सुमंत [मेरी टिप्पणी को एक समरूप पोस्ट से कहीं और कॉपी करना] क्या वह विनम्र गड़बड़ वास्तव में प्रदर्शन या स्मृति उपयोग के लिए कोई औसत दर्जे का लाभ प्रदान करता है, और यदि हां, तो इस तरह के लाभों की पर्याप्त मात्रा में पर्याप्त रूप से ऑफसेट करने के लिए यह कितना भयानक दिखता है और तथ्य यह है कि यह पता लगाने में लगभग एक घंटे लगते हैं कि यह क्या करने की कोशिश कर रहा है? मैं थोड़े शक करता हूं।
अंडरस्कोर_ड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.