如何使我的uninitialised_allocator安全

How to make my uninitialised_allocator safe?

本文关键字:allocator 安全 uninitialised 何使 我的      更新时间:2023-10-16

根据这个问题,我想使用unitialised_allocatorstd::vector一起使用,以避免在构造时元素的默认初始化(或std::vectorresize())(也见这里的用例)。我现在的设计是这样的:

// 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>>),但我得到的只是clang 3.2的崩溃…

当然,我认为设计可以简化,假设一个符合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. U上模板construct成员。如果您想将此分配器用于需要分配T(例如std::list)以外的其他内容的容器,则可以使用此分配器。

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