避免在标准容器中默认构建元素
我有兴趣构建一个 uninitialized_vector
容器,它在语义上与 std::vector
相同,但需要注意的是,否则将使用将在没有初始化的情况下创建无参数构造函数.我主要对避免将 POD 初始化为 0 感兴趣.据我所知,没有办法通过将 std::vector
与特殊类型的分配器结合来实现这一点.
I'm interested in building an uninitialized_vector
container, which will be semantically identical to std::vector
with the caveat that new elements which otherwise would be created with a no-argument constructor will instead be created without initialization. I'm primarily interested in avoiding initializing POD to 0. As far as I can tell, there's no way to accomplish this by combining std::vector
with a special kind of allocator.
我想以与 std::stack
相同的方式构建我的容器,它适应用户提供的容器(在我的例子中,std::vector
).换句话说,我想避免重新实现整个 std::vector
而是在它周围提供一个外观".
I'd like to build my container in the same vein as std::stack
, which adapts a user-provided container (in my case, std::vector
). In other words, I'd like to avoid reimplementing the entirety of std::vector
and instead provide a "facade" around it.
有没有一种简单的方法可以从 std::vector
的外部"控制默认构造?
Is there a simple way to control default construction from the "outside" of std::vector
?
这是我得到的解决方案,这启发了 Kerrek 的回答:
Here's the solution I arrived at, which was inspired Kerrek's answer:
#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.
This solution will work depending on how closely a particular vendor's compiler & STL's std::vector
implementation conforms to c++11.
推荐答案
我认为问题归结为容器对元素执行的初始化类型.比较:
I think the problem boils down to the type of initialization that the container performs on elements. Compare:
T * p1 = new T; // default-initalization
T * p2 = new T(); // value-initialization
标准容器的问题在于它们采用默认参数进行值初始化,如 resize(size_t, T = T())
.这意味着没有优雅的方法可以避免值初始化或复制.(对于构造函数也是如此.)
The problem with the standard containers is that they take the default argument to be value initialized, as in resize(size_t, T = T())
. This means that there's no elegant way to avoid value-initialization or copying. (Similarly for the constructor.)
即使使用标准分配器也不起作用,因为它们的中心 construct()
函数接受了一个值初始化的参数.您宁愿需要的是一个使用默认初始化的 construct()
:
Even using the standard allocators doesn't work, because their central construct()
function takes an argument that becomes value-initialized. What you would rather need is a construct()
that uses default-initialization:
template <typename T>
void definit_construct(void * addr)
{
new (addr) T; // default-initialization
}
这样的东西将不再是符合标准的分配器,但您可以围绕这个想法构建自己的容器.
Such a thing wouldn't be a conforming standard allocator any more, but you could build your own container around that idea.
相关文章