Встраивание массива конструируемых объектов, отличных от заданных по умолчанию, в классе C ++ - PullRequest
3 голосов
/ 25 апреля 2010

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) Кажется ли работоспособным взлом, который я придумал? Какие проблемы? (В основном меня интересует C ++ 0x в новых версиях GCC).

2) Есть ли лучший способ встроить в массив массив конструируемых объектов, отличных от заданных по умолчанию?

Ответы [ 3 ]

1 голос
/ 25 апреля 2010

Выравнивание памяти может нарушиться, поскольку Knot считает, что в нем нет ничего кроме символов. Помимо этого трюк работоспособен. Еще один более общий прием, который я видел, заключается в предоставлении функций-вставок, которые возвращают необработанную память для заполнения вызывающей стороной, например:

SuperOverOptimisedVector<Gordian> v;
...
Gordian* g = v.append();
new (g) Gordian(42);

Внешность может быть обманчива, поэтому я объясню. Функция v.append () не выделяет необработанную память из кучи. Он просто находит следующий доступный слот в векторе (изменяет размеры и копирует, если емкость исчерпана, так же, как это делает std :: vector), и передает адрес этого слота обратно вызывающей стороне.

Этот трюк, хотя и милый и умный, несколько странен и подвержен ошибкам. Это можно частично смягчить, придерживаясь одношагового соглашения:

new (v.append()) Gordian(42);

Но я предпочитаю рассматривать это как интересное любопытство, которого, как правило, следует избегать.

Таким образом, в общем, да, вы можете хранить не-конструируемые по умолчанию объекты в непрерывном массиве, но если разница в производительности не достаточно велика, чтобы повлиять на успех вашего проекта, просто используйте std :: vector.

1 голос
/ 25 апреля 2010

Основываясь на полученном ответе и моем первоначальном хакере, я придумал это общее решение, используя boost :: align_storage . В основном тип void, но для конструкций.

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);
1 голос
/ 25 апреля 2010

С одной стороны, вы можете использовать оболочку массива (например, 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);
        }
    }
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...