2011-08-28 34 views
9

我有兴趣构建一个容器uninitialized_vector,它在语义上与std::vector完全相同,但要注意的是,否则将使用无参数构造函数创建的新元素将不会创建初始化。我主要想避免将POD初始化为0. 据我所知,没有办法通过将 std::vector与一种特殊的分配器结合来实现此目的。 避免标准容器中元素的默认构造

我想以与std::stack相同的方式构建我的容器,它适应用户提供的容器(在本例中为std::vector)。换句话说,我想避免重新实现整个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:这就是你不再使用用户定义的构造函数POD的提示... –

回答

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

感谢您的回答,但我不明白您对'''construct()'''采取了一个变量值初始化的评论。为什么它不是一个没有操作? –

+0

@Jared:我只是指标准容器中使用标准分配器的方式 - 它的'construct()'函数通常是类似于'construct(void *,const T&)'的东西,所以在构造过程中至少有一个副本,但我想象这个函数无论如何都会被一个值初始化的对象调用,比如'construct(p,T())',所以你不能在标准容器内部逃避值初始化。 (即使是C++ 11的版本分配器也无法帮到你,因为你不能“移动”多于一次的东西......) –

+0

所以基本上我在说你可以安装一个容器,它的行为非常像'vector' ,而不是调用标准分配器,而是调用'new(addr)T;'而不是调用'construct()'。您可以保留分配器的其他方面(即内存分配)。 –

1

我不相信这是可能的包裹一个向量(适用于每种类型),除非您调整向量的大小,每增加一个和删除操作

如果你可以放弃包装STL容器,你可以通过在堆上保存一个char的数组,并为每个想要构建的对象使用放置new来实现。这样你就可以准确地控制对象的构造函数和析构函数何时被逐个调用。

6

而不是使用容器周围的包装,可以考虑使用周围的元素类型的包装:

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

那还是默认构造'value'吗? –

+0

@Seth:如果'T'是POD,'value'将保持未初始化。 –

+0

啊,没有意识到他只使用POD类型。 –