2011-08-28 4 views
9

컨테이너를 만드는 데 관심이 있습니다.이 컨테이너는 의미가 없으므로 std::vector과 동일한 의미로 사용되며, 인수가없는 생성자로 생성되는 새 요소는 대신 생성되지 않습니다. 초기화. 나는 주로 POD를 0으로 초기화하는 것을 피하는 것에 관심이있다. 내가 알 수있는 한, std::vector을 특별한 종류의 할당 자와 결합하여 이것을 달성 할 방법이 없다. 표준 컨테이너에서 요소의 기본 구성 피하기

사용자 제공 컨테이너 (내 경우 std::vector)를 채택하는 std::stack과 같은 컨테이너에 컨테이너를 구축하고 싶습니다. 즉, 나는 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; 
} 

이 솔루션은 특정 공급 업체의 컴파일러 & STL의 std::vector 구현은 C++ (11)을 준수하는 정도에 따라 작동합니다.

+0

아무것도하지 않는 기본 생성자를 정의 할 수 있습니까? 나는 컴파일러가 인라인을 할 수도 있고, 최적화가 설정된 상태에서 생략 할 수도있다. –

+0

@Doug T.- 기본 생성자의 경우를 해결할 수 있지만 흥미로운 내용은'''resize''에서 발생하는 것 같습니다. 생성자를 호출하지 않고'''resize''와 같은 함수를 호출하는 방법이 불분명합니다. –

+1

@Doug : 그건 당신이 더 이상 사용자 정의 생성자를 가진 포드가 아니라는 경고가 있습니다 ... –

답변

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

@ Jared : 표준 컨테이너에서 표준 할당자가 사용되는 방식을 의미합니다. 'construct()'함수는 일반적으로'construct (void *, const T &)'와 같으므로 작성 중에 적어도 하나의 복사본이 있습니다. 하지만이 함수는'construct (p, T()) '와 같이 값으로 초기화 된 객체로 호출되므로 표준 컨테이너에서 값 초기화를 이스케이프 처리 할 수 ​​없다고 상상해보십시오. (심지어 C++ 11 emplacing allocators는 당신이 어떤 것을 "한 번 이상"움직일 수 없기 때문에 당신을 도울 수 없다 ...) –

+0

그래서 기본적으로 나는'vector'와 매우 비슷한 동작을하는 컨테이너를 만들 수 있다고 말하고있다. , 표준 할당자를 호출하는 대신'construct()'를 호출하는 대신'new (addr) T;를 사용하면된다. 할당 자의 다른 측면 (예 : 메모리 할당)을 유지할 수 있습니다. –

1

을 추가 할 때마다 에 벡터의 크기를 조정하지 않는 한 벡터를 래핑하면 모든 종류의 벡터를 래핑 할 수 있다고 생각하지 않습니다.

STL 컨테이너를 포기할 수있는 경우 배열을 char으로 유지하고 힙에 배열을 구성하고 new을 사용하여 구성 할 수 있습니다. 이 방법을 사용하면 객체의 생성자와 소멸자가 하나씩 호출 될 때이를 정확하게 제어 할 수 있습니다.

6

대신 컨테이너 래퍼를 사용하는 요소 유형의 래퍼를 사용하는 것이 좋습니다 :

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

여전히'value'를 기본으로 구성하지 않습니까? –

+0

@Seth :'T'가 POD이면'value'는 초기화되지 않은 상태로 남을 것입니다. –

+0

아, 그는 POD 유형 만 사용하고 있다는 것을 몰랐습니다. –

관련 문제