क्या मैं केवल-केवल प्रकार के वेक्टर की सूची बना सकता हूं?


95

अगर मैं अपने GCC 4.7 स्नैपशॉट के माध्यम से निम्न कोड पास करता हूं, तो यह unique_ptrवेक्टर में s को कॉपी करने की कोशिश करता है ।

#include <vector>
#include <memory>

int main() {
    using move_only = std::unique_ptr<int>;
    std::vector<move_only> v { move_only(), move_only(), move_only() };
}

जाहिर है कि std::unique_ptrनकल योग्य नहीं होने के कारण काम नहीं हो सकता है:

त्रुटि: हटाए गए फ़ंक्शन 'std :: unique_ptr <_Tp, _Dp> :: unique_ptr (const std :: unique_ptr <_Tp, _Dp> &) [with _Tp = int; _Dp = std :: default_delete; std :: unique_ptr <_Tp, _Dp> = std :: unique_ptr] '

क्या जीसीसी प्रारंभिक बिंदु से पॉइंटर्स को कॉपी करने की कोशिश में सही है?


विजुअल स्टूडियो और क्लैंग का व्यवहार एक जैसा है
जीन-साइमन ब्रोचू

जवाबों:


46

का सारांश <initializer_list>18.9 बनाता में यह यथोचित एक प्रारंभकर्ता सूची की है कि तत्वों को स्पष्ट हमेशा स्थिरांक-संदर्भ के माध्यम से पारित कर रहे हैं। दुर्भाग्य से, भाषा के वर्तमान संशोधन में इनिशियलाइज़र सूची तत्वों में चाल-शब्दार्थ का उपयोग करने का कोई तरीका नहीं दिखता है।

विशेष रूप से, हमारे पास:

typedef const E& reference;
typedef const E& const_reference;

typedef const E* iterator;
typedef const E* const_iterator;

const E* begin() const noexcept; // first element
const E* end() const noexcept; // one past the last element

4
Cpptruths पर वर्णित <T> मुहावरे पर विचार करें ( cpptruths.blogspot.com/2013/09/… )। विचार रन-टाइम पर lvalue / rvalue निर्धारित करना है और फिर कॉल मूव या कॉपी-कंस्ट्रक्शन है। में <t> rvalue / lvalue का पता लगाएगा, भले ही initializer_list द्वारा प्रदान किया गया मानक इंटरफ़ेस का संदर्भ है।
सुमंत

3
@ सुमंत इतना "मुहावरेदार नहीं" लगता है: है ना, इसके बजाय, शुद्ध यूबी? न केवल इट्रेटर के रूप में, बल्कि अंतर्निहित तत्व स्वयं भी हो सकते हैं const, जिन्हें एक सुव्यवस्थित कार्यक्रम में दूर नहीं रखा जा सकता है।
अंडरस्कोर_ड

62

संपादित करें: चूंकि @ जोहान्स एक उत्तर के रूप में सबसे अच्छा समाधान पोस्ट नहीं करना चाहते हैं, मैं बस करूँगा।

#include <iterator>
#include <vector>
#include <memory>

int main(){
  using move_only = std::unique_ptr<int>;
  move_only init[] = { move_only(), move_only(), move_only() };
  std::vector<move_only> v{std::make_move_iterator(std::begin(init)),
      std::make_move_iterator(std::end(init))};
}

द्वारा लौटाए std::make_move_iteratorजाने वाले पुनरावृत्त पॉइंट-इन तत्व को स्थानांतरित कर दिए जाएंगे जब उन्हें अस्वीकृत किया जाएगा।


मूल उत्तर: हम यहाँ एक छोटे सहायक प्रकार का उपयोग करने वाले हैं:

#include <utility>
#include <type_traits>

template<class T>
struct rref_wrapper
{ // CAUTION - very volatile, use with care
  explicit rref_wrapper(T&& v)
    : _val(std::move(v)) {}

  explicit operator T() const{
    return T{ std::move(_val) };
  }

private:
  T&& _val;
};

// only usable on temporaries
template<class T>
typename std::enable_if<
  !std::is_lvalue_reference<T>::value,
  rref_wrapper<T>
>::type rref(T&& v){
  return rref_wrapper<T>(std::move(v));
}

// lvalue reference can go away
template<class T>
void rref(T&) = delete;

अफसोस की बात है, यहाँ सीधे-सीधे कोड काम नहीं करेगा:

std::vector<move_only> v{ rref(move_only()), rref(move_only()), rref(move_only()) };

मानक के बाद से, जो भी कारण के लिए, इस तरह एक परिवर्तित प्रतिलिपि निर्माता को परिभाषित नहीं करता है:

// in class initializer_list
template<class U>
initializer_list(initializer_list<U> const& other);

initializer_list<rref_wrapper<move_only>>ब्रेस-init-सूची (द्वारा बनाई गई {...}) में परिवर्तित नहीं होगा initializer_list<move_only>कि vector<move_only>लगता है। इसलिए हमें यहां दो-चरणीय आरंभ की आवश्यकता है:

std::initializer_list<rref_wrapper<move_only>> il{ rref(move_only()),
                                                   rref(move_only()),
                                                   rref(move_only()) };
std::vector<move_only> v(il.begin(), il.end());

1
आह ... यह std::ref, गैर का रूवल एनालॉग है ? शायद इसे बुलाया जाए std::rref
केरेक एसबी

17
अब, मुझे लगता है कि यह एक टिप्पणी में उल्लेख किए बिना नहीं छोड़ा जाना चाहिए :) move_only m[] = { move_only(), move_only(), move_only() }; std::vector<move_only> v(std::make_move_iterator(m), std::make_move_iterator(m + 3));
जोहान्स स्काउब -

1
@ जोहान्स: कभी-कभी, यह सरल समाधान है जो सिर्फ मुझे हटा देता है। हालांकि मैं मानता हूँ, मैं move_iteratorअभी तक उन लोगों के साथ परेशान नहीं किया था ।
Xeo

2
@ जोहान्स: इसके अलावा, ऐसा क्यों नहीं है? :)
Xio

1
@JohanLundberg: मैं इस बात पर विचार करूंगा कि क्यूओआई मुद्दा है, लेकिन मैं यह नहीं देखता कि यह ऐसा क्यों नहीं कर सकता। उदाहरण के लिए VC ++ का stdlib iterator श्रेणी के आधार पर टैग-प्रेषण और std::distanceफॉरवर्ड-या-बेहतर पुनरावृत्तियों के लिए उपयोग करता है और std::move_iteratorअंतर्निहित पुनरावृत्ति श्रेणी को एडाप्ट करता है । वैसे भी, अच्छा और संक्षिप्त समाधान। उत्तर के रूप में पोस्ट करें, हो सकता है?
Xeo

10

जैसा कि अन्य उत्तरों में उल्लेख किया गया है, का व्यवहार std::initializer_listवस्तुओं को मूल्य से पकड़ना है और बाहर जाने की अनुमति नहीं है, इसलिए यह संभव नहीं है। यहाँ एक संभावित वर्कअराउंड है, एक फंक्शन कॉल का उपयोग करते हुए जहां इनिशियलाइज़र को वैरिएड तर्कों के रूप में दिया जाता है:

#include <vector>
#include <memory>

struct Foo
{
    std::unique_ptr<int> u;
    int x;
    Foo(int x = 0): x(x) {}
};

template<typename V>        // recursion-ender
void multi_emplace(std::vector<V> &vec) {}

template<typename V, typename T1, typename... Types>
void multi_emplace(std::vector<V> &vec, T1&& t1, Types&&... args)
{
    vec.emplace_back( std::move(t1) );
    multi_emplace(vec, args...);
}

int main()
{
    std::vector<Foo> foos;
    multi_emplace(foos, 1, 2, 3, 4, 5);
    multi_emplace(foos, Foo{}, Foo{});
}

दुर्भाग्यवश multi_emplace(foos, {});विफल हो जाता है क्योंकि यह प्रकार के लिए कटौती नहीं कर सकता है {}, इसलिए ऑब्जेक्ट को डिफ़ॉल्ट रूप से निर्मित करने के लिए आपको वर्ग नाम दोहराना होगा। (या उपयोग vector::resize)


4
पुनरावर्ती पैक विस्तार को डमी सरणी अल्पविराम ऑपरेटर हैक द्वारा प्रतिस्थापित किया जा सकता है, कोड की कुछ पंक्तियों को बचाने के लिए
एमएम

0

जोहानस स्कैब की चाल के std::make_move_iterator()साथ std::experimental::make_array(), आप एक सहायक समारोह का उपयोग कर सकते हैं:

#include <memory>
#include <type_traits>
#include <vector>
#include <experimental/array>

struct X {};

template<class T, std::size_t N>
auto make_vector( std::array<T,N>&& a )
    -> std::vector<T>
{
    return { std::make_move_iterator(std::begin(a)), std::make_move_iterator(std::end(a)) };
}

template<class... T>
auto make_vector( T&& ... t )
    -> std::vector<typename std::common_type<T...>::type>
{
    return make_vector( std::experimental::make_array( std::forward<T>(t)... ) );
}

int main()
{
    using UX = std::unique_ptr<X>;
    const auto a  = std::experimental::make_array( UX{}, UX{}, UX{} ); // Ok
    const auto v0 = make_vector( UX{}, UX{}, UX{} );                   // Ok
    //const auto v1 = std::vector< UX >{ UX{}, UX{}, UX{} };           // !! Error !!
}

इसे लाइव देखें Coliru

शायद कोई अपनी बात को सीधे std::make_array()करने की अनुमति देने के make_vector()लिए प्रवंचना का लाभ उठा सकता है , लेकिन मैंने यह नहीं देखा कि कैसे (अधिक सटीक रूप से, मैंने कोशिश की कि मुझे लगा कि मुझे काम करना चाहिए, असफल हो गया और आगे बढ़ गया)। किसी भी स्थिति में, कंपाइलर को वेक्टर रूपांतरण में सरणी को इनलाइन करने में सक्षम होना चाहिए, जैसा कि क्लेंग ओ 2 पर होता है GodBolt


-1

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

यहाँ unique_ptrएक तर्क पैक का उपयोग करते हुए एक वेक्टर को आरंभ करने का मेरा कार्य है :

#include <iostream>
#include <vector>
#include <make_unique.h>  /// @see http://stackoverflow.com/questions/7038357/make-unique-and-perfect-forwarding

template <typename T, typename... Items>
inline std::vector<std::unique_ptr<T>> make_vector_of_unique(Items&&... items) {
    typedef std::unique_ptr<T> value_type;

    // Allocate memory for all items
    std::vector<value_type> result(sizeof...(Items));

    // Initialize the array in place of allocated memory
    new (result.data()) value_type[sizeof...(Items)] {
        make_unique<typename std::remove_reference<Items>::type>(std::forward<Items>(items))...
    };
    return result;
}

int main(int, char**)
{
    auto testVector = make_vector_of_unique<int>(1,2,3);
    for (auto const &item : testVector) {
        std::cout << *item << std::endl;
    }
}

यह एक भयानक विचार है। प्लेसमेंट नया एक हथौड़ा नहीं है, यह ठीक परिशुद्धता का एक उपकरण है। result.data()कुछ रैंडम मेमोरी का पॉइंटर नहीं है। यह किसी वस्तु का सूचक है । जब आप उस पर नया प्लेसमेंट करते हैं, तो उस खराब वस्तु का क्या होता है, इसके बारे में सोचें।
आर। मार्टिनो फर्नांडिस

इसके अतिरिक्त, प्लेसमेंट नए का सरणी रूप वास्तव में प्रयोग करने योग्य नहीं है stackoverflow.com/questions/8720425/…
आर। मार्टिनो फर्नांडीस

@R। मार्टिनो फर्नांडिस: यह बताने के लिए धन्यवाद कि सरणियों के लिए प्लेसमेंट-नया काम नहीं करेगा। अब मैं देखता हूं कि यह एक बुरा विचार क्यों था।
Gart
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.