如何使我的 uninitialised_allocator 安全? [英] How to make my uninitialised_allocator safe?

查看:28
本文介绍了如何使我的 uninitialised_allocator 安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据这个问题,我想使用一个 unitialised_allocator ,例如 std::vector 以避免在构造时默认初始化元素(或 std 的 resize()::vector(另见 这里是一个用例).我目前的设计是这样的:

Following from this question, I want to use an unitialised_allocator with, say, std::vector to avoid default initialisation of elements upon construction (or resize() of the std::vector (see also here for a use case). My current design looks like this:

// 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<> 模板可以这样定义:

Then an unitialised_vector<> template could be defined like this:

template<typename T, typename base_allocator = std::allocator<T>>
using uninitialised_vector =
  std::vector<T,uninitialised_allocator<T,base_allocator>>;

但是,正如我的评论所示,我不能 100% 确定 static_assert() 中的适当条件是什么?strong>(顺便说一句,可以考虑使用 SFINAE ——欢迎对此提出任何有用的意见)

However, as indicated by my comments, I'm not 100% certain as to what are the appropriate conditions in the static_assert()? (Btw, one may consider SFINAE instead -- any useful comments on this are welcome)

显然,人们必须避免因试图破坏未初始化对象而引发的灾难.考虑

Obviously, one has to avoid the disaster that would ensue from the attempted non-trivial destruction of an uninitialised object. Consider

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

It was suggested (comment by Evgeny Panasyuk) that I assert trivial constructibility, but this does not seem to catch the above disaster scenario. I just tried to check what clang says about std::is_trivially_default_constructible<std::vector<int>> (or std::is_trivially_destructible<std::vector<int>>) but all I got was a crash of clang 3.2 ...

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

Another, more advanced, option would be to design an allocator which only elides the default construction for objects for which this would be safe to do so.

推荐答案

Fwiw,我认为设计可以简化,假设一个符合 C++11 的容器:

Fwiw, I think the design can be simplified, assuming a C++11 conforming container:

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. 我认为从另一个分配器派生的优势不大.

  1. I see little advantage to deriving from another allocator.

现在你可以让 allocator_traits 处理 rebind.

Now you can let allocator_traits handle rebind.

模板化 U 上的 construct 成员.如果您想将此分配器与需要分配 T 以外的其他内容(例如 std::list)的容器一起使用,这将很有帮助.

Template the construct members on U. This helps if you want to use this allocator with some container that needs to allocate something other than a T (e.g. std::list).

static_assert 测试移到重要的单个 construct 成员中.

Move the static_assert test into the single construct member where it is important.

你仍然可以创建一个使用:

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 什么都不做.但我认为没有这样做的动机,因为我相信它应该在适当的时候得到优化.如果没有这样的限制,您可以:

I think the test for is_trivially_destructible is overkill, unless you also optimize destroy to do nothing. But I see no motivation in doing that since I believe it should get optimized anyway whenever appropriate. Without such a restriction you can:

class A
{
    int data_;
public:
    A() = default;
    A(int d) : data_(d) {}
};

int main()
{
    uninitialised_vector<A> v(10);
}

而且它确实有效.但是如果你让 ~A() 变得不平凡:

And it just works. But if you make ~A() non trivial:

    ~A() {std::cout << "~A(" << data_ << ")
";}

那么,至少在我的系统上,你会得到一个构造错误:

Then, at least on my system, you get an error on construction:

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 有一个非平凡的析构函数,它就不再是平凡可构造的.

I.e. A is no longer trivially constructible if it has a non-trivial destructor.

但是,即使使用非平凡的析构函数,您仍然可以:

However even with the non-trivial destructor you can still:

    uninitialised_vector<A> v;
    v.push_back(A());

这可行,因为我没有过度要求一个微不足道的析构函数.执行此操作时,我得到 ~A() 按预期运行:

This works, only because I didn't overreach with requiring a trivial destructor. And when executing this I get ~A() to run as expected:

~A(0)
~A(0)

这篇关于如何使我的 uninitialised_allocator 安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆