如何使我的未初始化分配器安全?

时间:2013-04-12 09:08:47

标签: c++ c++11 constructor containers allocator

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 ...

另一个更高级的选择是设计一个分配器,它只会为安全的对象省略默认构造。

1 个答案:

答案 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)...);
        }
};
  1. 我认为从另一个分配器派生出来没什么好处。

  2. 现在您可以让allocator_traits处理rebind

  3. construct U成员的模板。如果您想将此分配器与一些需要分配T之外的其他容器(例如std::list)的分配器一起使用,这会有所帮助。

  4. static_assert测试移至重要的单个construct成员。

  5. 您仍然可以创建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)