在C ++类中内联一组非默认的可构造对象

时间:2010-04-25 10:54:23

标签: c++ constructor

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)是否有更好的方法可以在类中内联一组非默认的可构造对象?

3 个答案:

答案 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);