C ++不允许包含非默认构造项的数组的类:
class Gordian {
public:
int member;
Gordian(int must_have_variable) : member(must_have_variable) {}
};
class Knot {
Gordian* pointer_array[8]; // Sure, this works.
Gordian inlined_array[8]; // Won't compile. Can't be initialized.
};
正如C ++用户所知,该语言保证在构造类时初始化所有非POD成员。并且它不信任用户初始化构造函数中的所有内容 - 在构造函数的主体开始之前,必须为所有成员的构造函数提供有效参数。
一般来说,就我而言,这是一个好主意,但我遇到过这样一种情况:如果我真的有一组非默认的可构造对象,那将会容易得多。
显而易见的解决方案:拥有一组指向对象的指针。这在我的情况下不是最佳的,因为我使用共享内存。它会迫使我从已经竞争的资源(即共享内存)中进行额外的分配。我希望在对象中内联数组的全部原因是减少分配数量。
如果有效,我会愿意使用黑客,甚至是丑陋的黑客。我正在考虑的一个可能的黑客是:
class Knot {
public:
struct dummy { char padding[sizeof(Gordian)]; };
dummy inlined_array[8];
Gordian* get(int index) {
return reinterpret_cast<Gordian*>(&inlined_array[index]);
}
Knot() {
for (int x = 0; x != 8; x++) {
new (get(x)) Gordian(x*x);
}
}
};
当然,它编译,但我不是一个经验丰富的C ++程序员。也就是说,我不太可能相信我的黑客。所以,问题:
1)我想出的黑客看起来是否可行?有什么问题? (我主要关注较新版GCC上的C ++ 0x)。
2)是否有更好的方法可以在类中内联一组非默认的可构造对象?
答案 0 :(得分:1)
首先,您可以使用数组包装器(例如boost::array
)来使用固定值初始化数组:
#include <boost/array.hpp>
class Gordian {
public:
int member;
Gordian(int must_have_variable) : member(must_have_variable) {}
};
namespace detail
{
boost::array<Gordian, 8> chop()
{
boost::array<Gordian, 8> t = {{0, 1, 4, 9, 16, 25, 36, 49}};
return t;
}
}
class Knot {
boost::array<Gordian, 8> array;
public:
Knot(): array(detail::chop()) {}
};
另一种可能性是boost::optional
数组(但会有一些大小开销):
#include <boost/optional.hpp>
class Gordian {
public:
int member;
Gordian(int must_have_variable) : member(must_have_variable) {}
};
class Knot {
boost::optional<Gordian> array[8];
public:
Knot()
{
for (int x = 0; x != 8; x++) {
array[x] = Gordian(x*x);
}
}
};
答案 1 :(得分:1)
内存对齐可能会中断,因为Knot
认为它只包含字符。除此之外,这个技巧是可行的。我见过的另一个更普遍的技巧是提供插入成员函数,它返回由调用者填充的原始内存,例如:
SuperOverOptimisedVector<Gordian> v;
...
Gordian* g = v.append();
new (g) Gordian(42);
外表可能是骗人的,所以我会解释。 v.append()函数不会从堆中分配原始内存。它只是找到向量中的下一个可用插槽(如果容量耗尽则调整大小和复制,与std :: vector相同)并将该插槽的地址传递回调用者。
这个技巧虽然可爱又聪明,但却有点怪异且容易出错。这可以通过遵循一步式惯例来部分缓解:
new (v.append()) Gordian(42);
但我更喜欢将它视为一种有趣的好奇心,通常应该避免。
总而言之,是的,您可以将非默认构造对象存储在连续数组中,但除非性能差异足以影响项目的成功,否则请使用std :: vector。
答案 2 :(得分:1)
根据我得到的答案以及我最初的黑客行为,我使用boost::aligned_storage提出了这个通用解决方案。基本上是一种空洞类型,但对于结构而言。
class Gordian {
public:
int member;
Gordian(int must_have_variable) : member(must_have_variable) {}
};
template <class T>
struct VoidWrap {
boost::aligned_storage<sizeof(T)> storage;
T* address() { return reinterpret_cast<T*>(storage.address()); }
};
class Knot {
public:
VoidWrap<Gordian> void_gordian[8];
Knot() {
for (int x = 0; x != 8; x++) {
new (void_gordian[x].address()) Gordian(x*x);
}
}
};
或者,一个专门用于1的用例的扩展版本。在包含它的对象的构造函数中初始化。 2)访问。 3)可能重新分配值。 4)适当的自动销毁。 (当然,如果在没有初始化的情况下销毁/访问它会爆炸)
template <class T>
struct VoidWrap {
boost::aligned_storage<sizeof(T)> storage;
/// Returns an address on which to construct the object. Use only once.
T* construct() { return access(); }
/// access should only be called on a `construct`ed object
/// obj.access() is equivalent to &obj
T* access() { return reinterpret_cast<T*>(this); }
/// Assumes the object has been constructed. Calls the destructor on the old
/// object, then returns an address on which to construct a new object.
T* reconstruct() {
access()->~T();
return access();
}
~VoidWrap() {
access()->~T();
}
};
VoidWrap<Gordian> void_g;
new (void_g.construct()) Gordian(10);
cout << void_g.access()->member << endl;
new (void_g.reconstruct()) Gordian(20);