在需要之前避免初始化成员

时间:2014-05-07 12:38:11

标签: c++ class memory c++11 initialization

我正在创建一个可以存储AtomManager<T>个对象的通用Atom<T>容器。

enum State { Alive, Dead, Unused };
template<class T> struct Atom
{
    T impl;
    int index, counter;
    State state;
};

我希望impl不要在堆上,因为我会在管理器中连续存储Atom<T>个实例。

AtomManager<T>将原子存储在std::vector<Atom<T>>中,如下所示:

| A | A | A | A | A | A | U | U | U | U |

A表示活着,U表示未使用。当用户调用AtomManager<T>::refresh()时,state等于State::Dead的所有原子将在存储结束时移动,然后它们将设置为State::Unused。例如:

| A | A | A | A | A | A | U | U | U | U |

// some atoms die

| A | D | A | D | A | A | U | U | U | U |

// user calls refresh()

| A | A | A | A | U | U | U | U | D | D |

// after refresh()

| A | A | A | A | U | U | U | U | U | U |

为了创建原子,我有一个匹配T构造函数签名的函数,这要归功于可变参数模板,并在从存储开始开始的第一个未使用的原子中构造T

问题是T必须默认可构建(因为我在resize()上调用了std::vector)。但这是我不需要的,因为当状态为Atom<T>::implState::Alive时,我只关心State::Dead。如果原子存活或死亡,则意味着用户先前使用前面提到的可变参数函数构造它。

但是,当原子未使用时(Atom<T>::index是垃圾),我确实关心Atom<T>::counterAtom<T>::impl

当原子未使用时,我不关心Atom<T>::impl的状态。

然而,编译器确实如此。 <{1}}不能默认构建时,我无法使用AtomManager<T>

我尝试在T内使用联盟:

Atom<T>

...但我无法让它正常工作。

如何在union { T impl; char dummy; }; 内存储T未构造的未初始化实例?

我不关心它的状态。我确定在访问之前我会正确构建它。但是当原子未被使用时,我希望它处于未定义的状态。

实现这一目标的最佳方式是什么?我不希望Atom<T>存储在堆上。

我不想引入其他依赖项。我无需查询Atom<T>::impl的状态,因为我知道可以安全地访问它。

3 个答案:

答案 0 :(得分:4)

简单的回答是使用boost::optional,但是你说你出于某种原因不想这样做。

要滚动自己的可选类型,您需要一个字节数组,适合T对齐。在C ++ 11或更高版本中,这很简单:

alignas(T) char bytes[sizeof(T)];

如果您坚持使用历史悠久的方言,那么您可能必须使用特定于编译器的扩展来指定对齐方式,或者只是希望获得最佳方案。

现在您可以使用placement-new:

创建对象
T * impl = new(bytes) T(...);

访问它的最简单方法是通过一个函数:

T & get_impl() {return *reinterpret_cast<T*>(bytes);}

并且不要忘记销毁它(但只有你创造它):

get_impl().~T();

答案 1 :(得分:3)

aligned_storageplacement newexplicit destructor致电救援!

enum State { Alive, Dead, Unused };
template<class T> struct Atom
{
    typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type impl;
    T& data() { return *reinterpret_cast<T*>(&impl); }
    T const& data() const { return *reinterpret_cast<T*>(&impl); }

    int index, counter;
    State state;

    template<typename... V>
    void create(V&&... args)
    {
        new(static_cast<void*>(&impl))T(std::forward<V>(args)...);
        state = Alive;
    }

    void destroy() { data().~T(); state = Unused; }

    ~Atom() { if(state != Unused) destroy(); }

    Atom(Atom const& other)
        : index(other.index),
        counter(other.counter),
        state(other.state)
    {
        if(state != Unused)
            new(static_cast<void*>(&impl))T(other.data());
    }

    Atom(Atom&& other)
        : index(other.index),
        counter(other.counter),
        state(other.state)
    {
        if(state != Unused)
            new(static_cast<void*>(&impl))T(std::move(other.data()));
    }
};

基本思想是从提供正确对齐的未初始化存储开始。然后,当你想要创建一个对象时,一个可变参数模板会将你所有的参数转发给一个placement new表达式,它基本上会调用该存储上的构造函数。最后,当你销毁它时(或者Atom的析构函数在它还没有Unused时被调用)时,会调用析构函数,以便内存不再代表一个对象。

需要庞大的复制和移动构造函数,以便副本能够正确映射到T的副本。为简洁起见,省略了编写赋值运算符(例如它)。

答案 2 :(得分:1)

您可以使用以下内容:

template<class T> struct Atom
{
public:
    ~Atom() { if (ptr) ptr->~T(); }

    template <typename...Ts>
    void create(Ts&&... args)
    {
        ptr = new (buffer) T(std::forward<Ts>(args)...);
    }

    T* get() { return ptr; }

private:
    T* ptr = nullptr;
    alignas(T) std::uint8_t buffer[sizeof(T)];
    int index, counter;
    State state;
};