我正在创建一个可以存储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>::impl
或State::Alive
时,我只关心State::Dead
。如果原子存活或死亡,则意味着用户先前使用前面提到的可变参数函数构造它。
Atom<T>::index
是垃圾),我确实关心Atom<T>::counter
和Atom<T>::impl
。
当原子未使用时,我不关心Atom<T>::impl
的状态。
然而,编译器确实如此。 <{1}}不能默认构建时,我无法使用AtomManager<T>
。
我尝试在T
内使用联盟:
Atom<T>
...但我无法让它正常工作。
如何在union { T impl; char dummy; };
内存储T
未构造的未初始化实例?
我不关心它的状态。我确定在访问之前我会正确构建它。但是当原子未被使用时,我希望它处于未定义的状态。
实现这一目标的最佳方式是什么?我不希望Atom<T>
存储在堆上。
我不想引入其他依赖项。我无需查询Atom<T>::impl
的状态,因为我知道可以安全地访问它。
答案 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_storage
,placement new
和explicit 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;
};