2011-08-28 6 views
9

मुझे uninitialized_vector कंटेनर बनाने में दिलचस्पी है, जो कि std::vector के साथ समान रूप से समान होगा, जो नए तत्वों को अन्यथा बिना किसी तर्क-निर्माता कन्स्ट्रक्टर के बनाया जाएगा, इसके बिना बनाया जाएगा प्रारंभ। मैं मुख्य रूप से पीओडी को 0. पर शुरू करने से बचने में रूचि रखता हूं, जहां तक ​​मैं कह सकता हूं, std::vector को एक विशेष प्रकार के आवंटक के साथ संयोजन करके इसे पूरा करने का कोई तरीका नहीं है। मानक कंटेनर में तत्वों के डिफ़ॉल्ट निर्माण से बचने

मैं std::stack, (, std::vector मेरे मामले में) जो एक उपयोगकर्ता द्वारा प्रदान की कंटेनर adapts के रूप में ही नस में मेरी कंटेनर का निर्माण करना चाहते हैं। दूसरे शब्दों में, मैं std::vector की पूरी तरह से पुन: कार्यान्वित करने से बचाना चाहता हूं और इसके बजाय इसके चारों ओर एक "मुखौटा" प्रदान करता हूं।

std::vector के "बाहरी" से डिफ़ॉल्ट निर्माण को नियंत्रित करने का कोई आसान तरीका है?


यहाँ समाधान मैं, पर पहुंचे जो Kerrek के जवाब प्रेरित था है:

#include <iostream> 
#include <vector> 
#include <memory> 
#include <algorithm> 
#include <cassert> 

// uninitialized_allocator adapts a given base allocator 
// uninitialized_allocator's behavior is equivalent to the base 
// except for its no-argument construct function, which is a no-op 
template<typename T, typename BaseAllocator = std::allocator<T>> 
    struct uninitialized_allocator 
    : BaseAllocator::template rebind<T>::other 
{ 
    typedef typename BaseAllocator::template rebind<T>::other super_t; 

    template<typename U> 
    struct rebind 
    { 
    typedef uninitialized_allocator<U, BaseAllocator> other; 
    }; 

    // XXX for testing purposes 
    typename super_t::pointer allocate(typename super_t::size_type n) 
    { 
    auto result = super_t::allocate(n); 

    // fill result with 13 so we can check afterwards that 
    // the result was not default-constructed 
    std::fill(result, result + n, 13); 
    return result; 
    } 

    // catch default-construction 
    void construct(T *p) 
    { 
    // no-op 
    } 

    // forward everything else with at least one argument to the base 
    template<typename Arg1, typename... Args> 
    void construct(T* p, Arg1 &&arg1, Args&&... args) 
    { 
    super_t::construct(p, std::forward<Arg1>(arg1), std::forward<Args>(args)...); 
    } 
}; 

namespace std 
{ 

// XXX specialize allocator_traits 
//  this shouldn't be necessary, but clang++ 2.7 + libc++ has trouble 
//  recognizing that uninitialized_allocator<T> has a well-formed 
//  construct function 
template<typename T> 
    struct allocator_traits<uninitialized_allocator<T> > 
    : std::allocator_traits<std::allocator<T>> 
{ 
    typedef uninitialized_allocator<T> allocator_type; 

    // for testing purposes, forward allocate through 
    static typename allocator_type::pointer allocate(allocator_type &a, typename allocator_type::size_type n) 
    { 
    return a.allocate(n); 
    } 

    template<typename... Args> 
    static void construct(allocator_type &a, T* ptr, Args&&... args) 
    { 
    a.construct(ptr, std::forward<Args>(args)...); 
    }; 
}; 

} 

// uninitialized_vector is implemented by adapting an allocator and 
// inheriting from std::vector 
// a template alias would be another possiblity 

// XXX does not compile with clang++ 2.9 
//template<typename T, typename BaseAllocator> 
//using uninitialized_vector = std::vector<T, uninitialized_allocator<T,BaseAllocator>>; 

template<typename T, typename BaseAllocator = std::allocator<T>> 
    struct uninitialized_vector 
    : std::vector<T, uninitialized_allocator<T,BaseAllocator>> 
{}; 

int main() 
{ 
    uninitialized_vector<int> vec; 
    vec.resize(10); 

    // everything should be 13 
    assert(std::count(vec.begin(), vec.end(), 13) == vec.size()); 

    // copy construction should be preserved 
    vec.push_back(7); 
    assert(7 == vec.back()); 

    return 0; 
} 

यह समाधान कैसे निकट एक विशेष विक्रेता की संकलक & एसटीएल के std::vector कार्यान्वयन C++ 11 के अनुरूप है के आधार पर काम करेंगे।

+0

क्या आप केवल एक डिफ़ॉल्ट निर्माता को परिभाषित कर सकते हैं जो कुछ भी नहीं करता? मैं शर्त लगाता हूं कि संकलक इनलाइन को ऑप्टिमाइज़ेशन के साथ इनलाइन कर सकता है या छोड़ सकता है। –

+0

@ डॉग टी।- यह डिफॉल्ट कन्स्ट्रक्टर के मामले को हल कर सकता है, लेकिन रोचक सामग्री '' 'आकार''' में होने लगती है - यह स्पष्ट नहीं है कि कन्स्ट्रक्टरों का आविष्कार किए बिना' '' आकार बदलें 'जैसे कार्यों को कैसे कॉल किया जाए। –

+1

@ डॉउग: इसमें चेतावनी है कि अब आप उपयोगकर्ता द्वारा परिभाषित कन्स्ट्रक्टर के साथ पीओडी नहीं हैं ... –

उत्तर

5

मुझे लगता है कि समस्या आरंभीकरण के प्रकार है कि कंटेनर तत्वों पर प्रदर्शन करने के लिए निर्भर करता है। की तुलना करें:

T * p1 = new T; // default-initalization 
T * p2 = new T(); // value-initialization 

मानक कंटेनर के साथ समस्या यह है कि वे मूल्य resize(size_t, T = T()) के रूप में, प्रारंभ करने के लिए डिफ़ॉल्ट तर्क ले। इसका मतलब है कि मूल्य-प्रारंभिकरण या प्रतिलिपि से बचने के लिए कोई शानदार तरीका नहीं है। (इसी प्रकार कन्स्ट्रक्टर के लिए।)

मानक आवंटकों का उपयोग करने से भी काम नहीं होता है, क्योंकि उनके केंद्रीय construct() फ़ंक्शन एक तर्क लेता है जो मूल्य-प्रारंभिक बन जाता है। क्या आप इसके बजाय की आवश्यकता होगी एक construct() डिफ़ॉल्ट-आरंभीकरण का उपयोग करता है:

template <typename T> 
void definit_construct(void * addr) 
{ 
    new (addr) T; // default-initialization 
} 

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

+0

इस उत्तर के लिए धन्यवाद, लेकिन मैं '' 'निर्माण()' '' के बारे में आपकी टिप्पणी को समझ नहीं पा रहा हूं जो मूल्य-प्रारंभिक बन जाता है। यह नो-ऑप क्यों नहीं हो सकता है? –

+0

@ जेरेड: मेरा मतलब यह है कि मानक आवंटकों में मानक आवंटकों का उपयोग किया जाता है - उनका 'निर्माण() 'फ़ंक्शन आमतौर पर' निर्माण (शून्य *, कॉन्स्ट टी एंड) 'जैसा होता है, इसलिए निर्माण के दौरान कम से कम एक प्रति होती है, लेकिन मुझे कल्पना है कि इस फ़ंक्शन को वैल्यू-प्रारंभिक ऑब्जेक्ट के साथ वैसे भी कहा जाएगा, जैसे 'निर्माण (पी, टी())', ताकि आप मानक कंटेनरों के अंदर मूल्य प्रारंभिकता से बच सकें। (यहां तक ​​कि सी ++ 11 आवंटित आवंटक भी आपकी मदद नहीं कर सकते हैं क्योंकि आप एक से अधिक बार "स्थानांतरित" नहीं कर सकते हैं ...) –

+0

तो मूल रूप से मैं कह रहा हूं कि आप एक कंटेनर को घुमा सकते हैं जो 'वेक्टर' , लेकिन मानक आवंटक को कॉल करने के बजाय, आप इसे 'निर्माण() 'कॉल करने के बजाय' नया (addr) टी; 'कहेंगे। आप आवंटक के अन्य पहलुओं को रख सकते हैं (यानी स्मृति आवंटन)। –

1

मुझे विश्वास नहीं है कि वे एक वेक्टर (जो हर प्रकार के साथ काम करता है) को लपेटकर संभव है, जब तक कि आप पर वेक्टर का आकार बदलते हैं और ऑपरेशन हटाते हैं।

आप एसटीएल कंटेनर लपेटकर छोड़ नहीं रहा हूं, तो आप ढेर पर char की एक सरणी रखने और वस्तुओं आप का निर्माण करना चाहते हैं में से प्रत्येक के लिए प्लेसमेंट new उपयोग करके ऐसा कर सकता है। इस तरह आप नियंत्रण कर सकते थे जब वस्तुओं के रचनाकारों और विनाशकों को एक-एक करके बुलाया गया था।

6

इसके बजाय कंटेनर के चारों ओर एक आवरण का उपयोग करने का, तत्व प्रकार के चारों ओर एक आवरण का उपयोग करें:

template <typename T> 
struct uninitialized 
{ 
    uninitialized() { } 
    T value; 
}; 
+1

क्या यह अभी भी डिफ़ॉल्ट 'मूल्य' का निर्माण नहीं करता है? –

+0

@ सेठ: यदि 'टी' पीओडी है, तो 'मान' को अनियंत्रित छोड़ा जाएगा। –

+0

आह, यह नहीं पता था कि वह केवल पीओडी प्रकारों का उपयोग कर रहा था। –

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