Любые обходные пути для инициализации массива нестатического элемента?

В С++ невозможно инициализировать элементы массива в списке инициализации, поэтому объекты-члены должны иметь конструкторы по умолчанию, и они должны быть правильно инициализированы в конструкторе. Существует ли (разумное) обходное решение для этого, кроме использования массивов?

[Все, что может быть инициализировано с использованием только списка инициализации, в нашем приложении гораздо предпочтительнее использовать конструктор, поскольку эти данные могут быть выделены и инициализированы компилятором и компоновщиком, и каждый цикл тактового сигнала ЦП рассчитывается даже до main. Однако не всегда возможно иметь конструктор по умолчанию для каждого класса, и, кроме того, повторная инициализация данных снова в конструкторе все равно побеждает цель.]

например. Я хотел бы иметь что-то вроде этого (но это не работает):

class OtherClass {
private:
    int data;
public:
    OtherClass(int i) : data(i) {}; // No default constructor!
};

class Foo {
private:
    OtherClass inst[3]; // Array size fixed and known ahead of time.
public:
    Foo(...)
        : inst[0](0), inst[1](1), inst[2](2)
        {};
};

Единственным обходным решением, о котором я знаю, является не массив:

class Foo {
private:
    OtherClass inst0;
    OtherClass inst1;
    OtherClass inst2;
    OtherClass *inst[3];
public:
    Foo(...)
        : inst0(0), inst1(1), inst2(2) {
        inst[0]=&inst0;
        inst[1]=&inst1;
        inst[2]=&inst2;
    };
};

Изменить: Следует подчеркнуть, что OtherClass не имеет конструктора по умолчанию и что очень желательно, чтобы компоновщик мог выделить любую необходимую память (один или несколько статических экземпляров Foo), используя кучу, по существу, verboten. Я обновил приведенные выше примеры, чтобы выделить первую точку.

Ответы

Ответ 1

Один из возможных способов обхода - избежать компилятора, вызывающего конструктор OtherClass, и вызвать его самостоятельно, используя новое место размещения, чтобы инициализировать его в зависимости от того, что вам нужно. Пример:

  class Foo
  {
  private:
    char inst[3*sizeof(OtherClass)]; // Array size fixed. OtherClass has no default ctor.

    // use Inst to access, not inst
    OtherClass &Inst(int i) {return (OtherClass *)inst+i;}
    const OtherClass &Inst(int i) const {return (const OtherClass *)inst+i;}
  public:
    Foo(...)
    {
      new (Inst(0)) OtherClass(...);
      new (Inst(1)) OtherClass(...);
      new (Inst(2)) OtherClass(...);
    }
    ~Foo()
    {
      Inst(0)->~OtherClass();
      Inst(1)->~OtherClass();
      Inst(2)->~OtherClass();
    }
  };

Чтобы удовлетворить возможные требования к выравниванию для OtherClass, вам может понадобиться использовать __declspec (align (x)) при работе в VisualС++ или использовать тип, отличный от char, например:

Type inst[3*(sizeof(OtherClass)+sizeof(Type)-1)/sizeof(Type)];

... где Type - это int, double, long long или что-то другое, описывающее требования к выравниванию.

Ответ 2

Какие данные находятся в OtherClass? Будет ли инициализация значения достаточной для этого класса?

Если инициализация значения достаточна, вы можете инициализировать значение в массиве инициализации массива:

class A {
public:
  A ()
  : m_a()  // All elements are value-initialized (which for int means zero'd)
  {
  }

private:
  int m_a[3];
};

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

РЕДАКТИРОВАТЬ: Только для пояснения комментария от Drealmer.

Если тип элемента не является POD, он должен иметь "доступный конструктор по умолчанию" (как указано выше). Если компилятор не может вызвать конструктор по умолчанию, это решение не будет работать.

Следующий пример не будет работать с этим подходом:

class Elem {
public:
   Elem (int);  // User declared ctor stops generation of implicit default ctor
};

class A {
public:
  A ()
  : m_a ()         // Compile error: No default constructor
  {}

private:
  Elem m_a[10];
};

Ответ 3

Один метод, который я обычно использую для того, чтобы член класса "появлялся" в стеке (хотя фактически хранился в куче):

class Foo {
private:
    int const (&array)[3];
    int const (&InitArray() const)[3] {
        int (*const rval)[3] = new int[1][3];
        (*rval)[0] = 2;
        (*rval)[1] = 3;
        (*rval)[2] = 5;
        return *rval;
    }
public:
    explicit Foo() : array(InitArray()) { }
    virtual ~Foo() { delete[] &array[0]; }
};
Для клиентов вашего класса массив имеет тип "int const [3]". Объедините этот код с новым местом размещения, и вы также сможете поистине инициализировать значения по своему усмотрению, используя любой желаемый конструктор. Надеюсь, это поможет.

Ответ 4

Элементы массива по умолчанию не инициализируются. Таким образом, вы можете использовать статическую вспомогательную функцию, которая выполняет инициализацию, и сохраняет результат вспомогательной функции в члене.

#include "stdafx.h"
#include <algorithm>
#include <cassert>

class C {
public: // for the sake of demonstration...
  typedef int t_is[4] ;
  t_is is;
  bool initialized;

  C() : initialized( false )
  {
  }

  C( int deflt )
    : initialized( sf_bInit( is, deflt ) )
  {}

  static bool sf_bInit( t_is& av_is, const int i ){
    std::fill( av_is, av_is + sizeof( av_is )/sizeof( av_is[0] ), i );
    return true;
  }
};

int _tmain(int argc, _TCHAR* argv[])
{

  C c(1), d;

  assert( c.is[0] == 1 );

  return 0;
}

Следует отметить, что в следующем стандарте они будут поддерживать инициализаторы массивов.

Ответ 5

Использовать наследование для создания прокси-объекта

class ProxyOtherClass : public OtherClass {
public:   
  ProxyOtherClass() : OtherClass(0) {}
};

class Foo {
private:
  ProxyOtherClass inst[3]; // Array size fixed and known ahead of time.
public:
  Foo(...) {}
};

Ответ 6

А как насчет использования массива указателей вместо массива объектов? Например:

class Foo {
private:
    OtherClass *inst[3];
public:
    Foo(...) {
        inst[0]=new OtherClass(1);
        inst[1]=new OtherClass(2);
        inst[2]=new OtherClass(3);
    };

    ~Foo() {
       delete [] inst;   
    }

};

Ответ 7

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

Итак, не используйте конструкторы. То есть, не используйте обычные "экземпляры". Объявите все статически. Когда вам нужен новый "экземпляр", создайте новое статическое объявление, потенциально вне любых классов. Используйте structs с публичными членами, если вам нужно. Используйте C, если вам нужно.

Вы ответили на свой вопрос. Конструкторы и деструкторы полезны только в средах с большим количеством распределения и освобождения. Какая польза от разрушения, если цель состоит в том, чтобы как можно больше данных было распределено статически, и что же такое хорошее строительство без разрушения? К черту их обоих.