从this question开始,我想使用unitialised_allocator
,例如std::vector
,以避免在构建时(或resize()
std::vector
默认初始化元素(另请参阅here了解用例)。我目前的设计如下所示:
// based on a design by Jared Hoberock
template<typename T, typename base_allocator >
struct uninitialised_allocator : base_allocator::template rebind<T>::other
{
// added by Walter Q: IS THIS THE CORRECT CONDITION?
static_assert(std::is_trivially_default_constructible<T>::value,
"value type must be default constructible");
// added by Walter Q: IS THIS THE CORRECT CONDITION?
static_assert(std::is_trivially_destructible<T>::value,
"value type must be default destructible");
using base_t = typename base_allocator::template rebind<T>::other;
template<typename U>
struct rebind
{
typedef uninitialised_allocator<U, base_allocator> other;
};
typename base_t::pointer allocate(typename base_t::size_type n)
{
return base_t::allocate(n);
}
// catch default construction
void construct(T*)
{
// 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)default_
{
base_t::construct(p, std::forward<Arg1>(arg1), std::forward<Args>(args)...);
}
};
然后可以像这样定义unitialised_vector<>
模板:
template<typename T, typename base_allocator = std::allocator<T>>
using uninitialised_vector =
std::vector<T,uninitialised_allocator<T,base_allocator>>;
但是,正如我的评论所示,我不能100%确定 static_assert()
中的适当条件是什么? (顺便说一下,可以考虑使用SFINAE - 欢迎任何有用的评论。
显然,人们必须避免因未经初始化的物体的非平凡破坏而导致的灾难。考虑
unitialised_vector< std::vector<int> > x(10); // dangerous.
有人建议(由Evgeny Panasyuk评论)我断言琐碎的可构造性,但这似乎没有抓住上述灾难情景。我只是试着检查一下clang对std::is_trivially_default_constructible<std::vector<int>>
(或std::is_trivially_destructible<std::vector<int>>
)所说的内容,但我所得到的只是铿锵声3.2 ...
另一个更高级的选择是设计一个分配器,它只会为安全的对象省略默认构造。
答案 0 :(得分:4)
Fwiw,我认为设计可以简化,假设符合C ++ 11的容器:
template <class T>
class no_init_allocator
{
public:
typedef T value_type;
no_init_allocator() noexcept {}
template <class U>
no_init_allocator(const no_init_allocator<U>&) noexcept {}
T* allocate(std::size_t n)
{return static_cast<T*>(::operator new(n * sizeof(T)));}
void deallocate(T* p, std::size_t) noexcept
{::operator delete(static_cast<void*>(p));}
template <class U>
void construct(U*) noexcept
{
static_assert(std::is_trivially_default_constructible<U>::value,
"This allocator can only be used with trivally default constructible types");
}
template <class U, class A0, class... Args>
void construct(U* up, A0&& a0, Args&&... args) noexcept
{
::new(up) U(std::forward<A0>(a0), std::forward<Args>(args)...);
}
};
我认为从另一个分配器派生出来没什么好处。
现在您可以让allocator_traits
处理rebind
。
construct
U
成员的模板。如果您想将此分配器与一些需要分配T
之外的其他容器(例如std::list
)的分配器一起使用,这会有所帮助。
将static_assert
测试移至重要的单个construct
成员。
您仍然可以创建using
:
template <class T>
using uninitialised_vector = std::vector<T, no_init_allocator<T>>;
这仍然无法编译:
unitialised_vector< std::vector<int> > x(10);
test.cpp:447:17: error: static_assert failed "This allocator can only be used with trivally default constructible types"
static_assert(std::is_trivially_default_constructible<U>::value,
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
我认为is_trivially_destructible
的测试是过度的,除非您还优化destroy
什么都不做。但我认为这样做没有动力,因为我认为无论如何都应该优化它。如果没有这样的限制,您可以:
class A
{
int data_;
public:
A() = default;
A(int d) : data_(d) {}
};
int main()
{
uninitialised_vector<A> v(10);
}
它只是有效。但是如果你让~A()
变得非常简单:
~A() {std::cout << "~A(" << data_ << ")\n";}
然后,至少在我的系统上,你会收到构造错误:
test.cpp:447:17: error: static_assert failed "This allocator can only be used with trivally default constructible types"
static_assert(std::is_trivially_default_constructible<U>::value,
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
即。 A
如果有一个非平凡的析构函数,则不再是简单的可构造的。
然而,即使使用非平凡的析构函数,您仍然可以:
uninitialised_vector<A> v;
v.push_back(A());
这有效,仅,因为我并没有过度要求一个简单的析构函数。执行此操作时,我会~A()
按预期运行:
~A(0)
~A(0)