2016-04-25 8 views
39

निम्नलिखित स्निपेट:क्या एक पूरी तरह से सामान्य स्वैप() फ़ंक्शन को परिभाषित करना ठीक है?

#include <memory> 
#include <utility> 

namespace foo 
{ 
    template <typename T> 
    void swap(T& a, T& b) 
    { 
     T tmp = std::move(a); 
     a = std::move(b); 
     b = std::move(tmp); 
    } 

    struct bar { }; 
} 

void baz() 
{ 
    std::unique_ptr<foo::bar> ptr; 
    ptr.reset(); 
} 

मेरे लिए संकलन नहीं करता है:

$ g++ -std=c++11 -c foo.cpp 
In file included from /usr/include/c++/5.3.0/memory:81:0, 
       from foo.cpp:1: 
/usr/include/c++/5.3.0/bits/unique_ptr.h: In instantiation of ‘void std::unique_ptr<_Tp, _Dp>::reset(std::unique_ptr<_Tp, _Dp>::pointer) [with _Tp = foo::bar; _Dp = std::default_delete<foo::bar>; std::unique_ptr<_Tp, _Dp>::pointer = foo::bar*]’: 
foo.cpp:20:15: required from here 
/usr/include/c++/5.3.0/bits/unique_ptr.h:342:6: error: call of overloaded ‘swap(foo::bar*&, foo::bar*&)’ is ambiguous 
    swap(std::get<0>(_M_t), __p); 
    ^
In file included from /usr/include/c++/5.3.0/bits/stl_pair.h:59:0, 
       from /usr/include/c++/5.3.0/bits/stl_algobase.h:64, 
       from /usr/include/c++/5.3.0/memory:62, 
       from foo.cpp:1: 
/usr/include/c++/5.3.0/bits/move.h:176:5: note: candidate: void std::swap(_Tp&, _Tp&) [with _Tp = foo::bar*] 
    swap(_Tp& __a, _Tp& __b) 
    ^
foo.cpp:7:10: note: candidate: void foo::swap(T&, T&) [with T = foo::bar*] 
    void swap(T& a, T& b) 

यह एक swap() समारोह तो सामान्य है कि यह std::swap का विरोध करता घोषित करने के लिए मेरी गलती है?

यदि हां, तो foo::swap() को परिभाषित करने का कोई तरीका है ताकि इसे कोएनिग लुकअप द्वारा नहीं लिया जा सके?

+0

जीसीसी या बजना पर संकलन नहीं करता है, लेकिन MSVC 2015 एक और गैर-दस्तावेजी सुविधा शायद पर संकलित करता है। – wally

+0

अरे, वे पहले घंटे से कम के भीतर कुछ अच्छे अनुपात हैं। +16 अपवॉट्स, 77 बार देखा गया, और 4 पसंदीदा। –

+1

आपके पास ऐसे सामान्य 'स्वैप' टेम्पलेट को परिभाषित करने का कोई कारण नहीं होना चाहिए जिसमें केवल विशिष्ट प्रकार वाले विशिष्ट नामस्थान हैं। 'Foo :: bar' के लिए बस एक गैर-टेम्पलेट' स्वैप 'अधिभार परिभाषित करें। 'Std :: swap' पर सामान्य स्वैपिंग छोड़ दें, और केवल विशिष्ट ओवरलोड प्रदान करें। – TemplateRex

उत्तर

25
  • unique_ptr<T>T* की आवश्यकता है एक NullablePointer [unique.ptr] p3
  • NullablePointerSwappable [nullablepointer.requirements] p1
  • Swappable अनिवार्य रूप से x के लिए एक अधिभार का चयन करने की आवश्यकता है using std::swap; swap(x, y); होने की T* की lvalues ​​की आवश्यकता हो सकता है, yT* [swappable.requirements] p3
के प्रकार के स्तर

अंतिम चरण में, आपका प्रकार foo::bar एक अस्पष्टता पैदा करता है और इसलिए unique_ptr की आवश्यकताओं का उल्लंघन करता है। libstdC++ का कार्यान्वयन अनुरूप है, हालांकि मैं कहूंगा कि यह आश्चर्यजनक है।


शब्द निश्चित रूप से थोड़ी अधिक दृढ़ है, क्योंकि यह सामान्य है।

[unique.ptr] p3

प्रकार remove_reference_t<D>::pointer मौजूद है, तो तो unique_ptr<T, D>::pointer remove_reference_t<D>::pointer के लिए एक पर्याय होगा। अन्यथा unique_ptr<T, D>::pointerT* के लिए समानार्थी होगा। प्रकार unique_ptr<T, D>::pointerNullablePointer की आवश्यकताओं को पूरा करेगा।

(जोर मेरा)

[nullablepointer.requirements] p1

एक NullablePointer प्रकार एक सूचक की तरह प्रकार है कि अशक्त मूल्यों का समर्थन करता है है। एक प्रकार P की आवश्यकताओं को पूरा करता है, तो NullablePointer:

  • [...]
  • प्रकार P की lvalues ​​हैं swappable (17.6.3.2),
  • [...]

[swappable।आवश्यकताओं] p2

एक वस्तु t एक वस्तु u यदि और केवल यदि साथ swappable है:

  • भाव swap(t, u) और जब संदर्भ नीचे वर्णित में मूल्यांकन swap(u, t) मान्य हैं, और
  • [.. ।]

[swappable.requirements] p3

+०१२३५१६४१०६१

संदर्भ में swap(t, u) और swap(u, t) मूल्यांकन किया जाता है यह सुनिश्चित करेगा कि एक द्विआधारी गैर सदस्य "स्वैप" नाम समारोह भी शामिल है कि एक उम्मीदवार सेट पर अधिभार संकल्प के माध्यम से चयन किया जाता है:

  • दो swap समारोह टेम्पलेट्स <utility> और
  • में देखने सेट तर्क पर निर्भर देखने द्वारा उत्पादित परिभाषित किया।

ध्यान दें कि एक सूचक प्रकार T* के लिए, ADL के प्रयोजनों के लिए, संबद्ध नामस्थान और कक्षाओं प्रकार T से हुआ है। इसलिए, foo::bar* में foo संबंधित नामस्थान के रूप में है। swap(x, y) के लिए ADL जहां या तो x या y एक foo::bar* है इसलिए foo::swap मिल जाएगा।

+0

आप उन लोगों के लिए एक सरल ब्रेकडाउन [यहां] (http://en.cppreference.com/w/cpp/concept/Swappable) पा सकते हैं जो वकील नहीं बोलते हैं। वैसे भी, जबकि libstdC++ निश्चित रूप से नियमों का पालन कर रहा है, वहां ऐसा कुछ भी प्रतीत नहीं होता है जिसके लिए इसे रीसेट के अंदर एडीएल स्वैप का उपयोग करने की आवश्यकता होती है, इस बात पर विचार करते हुए कि deckers के libcxx का स्वैप कार्यात्मक रूप से समतुल्य है। मैं इसे कार्यान्वयन की गुणवत्ता के लिए तैयार कर दूंगा। – user6253369

+0

@ user6253369 यह ओपी के हिस्से पर क्यूओआई है। बस एसटीएल टेम्पलेट्स के अनुकूलन बिंदुओं को डुप्लिकेट करने वाले सामान्य टेम्पलेट्स प्रदान न करें, और विशेष रूप से जब आप ऐसा करते हैं तो ऐसे एसटीएल टेम्पलेट्स को अपनी कक्षाएं प्रदान न करें। – TemplateRex

+1

@ user6253369 फैंसी पॉइंटर्स के लिए जो वास्तव में कस्टम स्वैप को परिभाषित करते हैं, उनका उपयोग करके अधिक कुशल हो सकता है। –

12

इस तकनीक foo::swap() ADL द्वारा पाया जा रहा से बचने के लिए इस्तेमाल किया जा सकता:

namespace foo 
{ 
    namespace adl_barrier 
    { 
     template <typename T> 
     void swap(T& a, T& b) 
     { 
      T tmp = std::move(a); 
      a = std::move(b); 
      b = std::move(tmp); 
     } 
    } 

    using namespace adl_barrier; 
} 

यह कैसे Boost.Range के बिना आधार के begin()/end() कार्यों में परिभाषित कर रहे है। मैंने सवाल पूछने से पहले कुछ ऐसा करने की कोशिश की, लेकिन इसके बजाय using adl_barrier::swap; किया, जो काम नहीं करता है।

कि क्या सवाल में टुकड़ा काम करना चाहिए के रूप में के रूप में किया जाता है, मुझे यकीन है कि नहीं हूँ। एक जटिलता है मैं देख सकता हूँ unique_ptr जो सामान्य using std::swap; swap(a, b); मुहावरा के साथ बदली किया जाना चाहिए Deleter से कस्टम pointer प्रकार, हो सकता है है। प्रश्न में foo::bar* के लिए यह मुहावरे स्पष्ट रूप से टूटा हुआ है।

+1

यदि यह "पूरी तरह से सामान्य" है, तो आप उपयोग-घोषणा के साथ 'foo' में' std :: swap' आयात क्यों नहीं करते हैं? – dyp

+1

@dyp 'foo :: swap() 'लिखा गया था क्योंकि हमारे प्लेटफॉर्म में से एक मानक पुस्तकालय है जिसमें एक' std :: swap()' (चाल के बजाय प्रतियां) है। मैं शायद उस प्लेटफॉर्म को छोड़कर 'std :: swap का उपयोग कर' करूँगा। –

+0

Btw, मुझे लगता है कि यह बेहतर करने के लिए अपने प्रकार ADL-रक्षा है (यानी 'foo :: bar') अपने कार्यों (यानी' foo :: swap') के बजाय, के बाद से उन दु: ख के कारण लोगों को, और कार्यों की संख्या रहे हैं 'नेमस्पेस फू' में ओपन-एंडेड है (कोई व्यक्ति किसी दिन 'foo :: start' और' foo :: end' जोड़ सकता है और फिर आपके पास नई परेशानी है, इसके विरुद्ध एडीएल-संरक्षित 'foo :: bar' गार्ड)। – TemplateRex

14

समस्या ++ के unique_ptr के कार्यान्वयन libstdc है। यह उनकी 4.9.2 शाखा से है:

https://gcc.gnu.org/onlinedocs/gcc-4.9.2/libstdc++/api/a01298_source.html#l00339

338  void 
    339  reset(pointer __p = pointer()) noexcept 
    340  { 
    341  using std::swap; 
    342  swap(std::get<0>(_M_t), __p); 
    343  if (__p != pointer()) 
    344  get_deleter()(__p); 
    345  } 

जैसा कि आप देख सकते हैं, वहाँ एक अयोग्य स्वैप कॉल है। अब चलो (libC++) libcxx देखने के कार्यान्वयन करते हैं:

https://git.io/vKzhF

_LIBCPP_INLINE_VISIBILITY void reset(pointer __p = pointer()) _NOEXCEPT 
{ 
    pointer __tmp = __ptr_.first(); 
    __ptr_.first() = __p; 
    if (__tmp) 
     __ptr_.second()(__tmp); 
} 

_LIBCPP_INLINE_VISIBILITY void swap(unique_ptr& __u) _NOEXCEPT 
    {__ptr_.swap(__u.__ptr_);} 

वे swapreset अंदर कॉल नहीं करते और न ही वे एक अयोग्य स्वैप कॉल का उपयोग करते हैं।


Dyp's answer क्यों libstdc++ अनुरूप है, लेकिन यह भी कारण है कि अपने कोड जब भी swap मानक पुस्तकालय से बुलाया जाना आवश्यक है टूट जाएगा पर एक बहुत ठोस विश्लेषण प्रदान करती है। TemplateRex के शब्दों में:

आप एक बहुत विशिष्ट केवल विशिष्ट प्रकार युक्त नाम स्थान में इस तरह के एक सामान्य swap टेम्पलेट परिभाषित करने के लिए कोई कारण नहीं होना चाहिए। foo::bar के लिए बस एक गैर-टेम्पलेट swap ओवरलोड को परिभाषित करें। सामान्य स्वैपिंग से std::swap पर छोड़ दें, और केवल विशिष्ट ओवरलोड प्रदान करें।source

उदाहरण के लिए, इस संकलन नहीं होगा:

std::vector<foo::bar> v; 
std::vector<foo::bar>().swap(v); 

आप एक पुराने मानक पुस्तकालय/जीसीसी (CentOS की तरह) के साथ एक मंच लक्षित कर रहे हैं, मैं बजाय बूस्ट का उपयोग कर की सिफारिश करेंगे इस तरह के नुकसान से बचने के लिए पहिया को फिर से शुरू करना।

+0

"मैं पहिया को पुनर्निर्मित करने के बजाय बूस्ट का उपयोग करने की सिफारिश करता हूं" सहमत! हम इसे 'foo' में कई चीजों के लिए करते हैं। दुर्भाग्य से 'boost :: swap() 'चाल-असाइनमेंट नहीं करता है, लेकिन केवल' std :: swap' को प्रतिनिधि करता है। –

+0

@TavianBarnes यह सच हो सकता है लेकिन मेरा मानना ​​है कि अद्वितीय_ptr के बूस्ट समकक्ष उदाहरण के लिए स्वैप के लिए सैमसंगिक्स चलाता है। – user6253369

+0

सही, लेकिन मैं अक्सर 'स्वैप (ए, बी) करना चाहता हूं;' जहां 'ए' और' बी' एक गैर-प्रतिलिपि प्रकार के होते हैं। * प्रत्येक * चाल-केवल प्रकार के परिभाषित करने के लिए एक अधिभार घोषित करना एक दर्द है, इसलिए मुझे एक 'स्वैप() 'कार्यान्वयन की आवश्यकता है जो चलता है। –

संबंधित मुद्दे