tags:

views:

2096

answers:

7

In C++, it's not possible to initialize array members in the initialization list, thus member objects should have default constructors and they should be properly initialized in the constructor. Is there any (reasonable) workaround for this apart from not using arrays?

[Anything that can be initialized using only the initialization list is in our application far preferable to using the constructor, as that data can be allocated and initialized by the compiler and linker, and every CPU clock cycle counts, even before main. However, it is not always possible to have a default constructor for every class, and besides, reinitializing the data again in the constructor rather defeats the purpose anyway.]

E.g. I'd like to have something like this (but this one doesn't work):

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)
        {};
};

The only workaround I'm aware of is the non-array one:

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;
    };
};

Edit: It should be stressed that OtherClass has no default constructor, and that it is very desirable to have the linker be able to allocate any memory needed (one or more static instances of Foo will be created), using the heap is essentially verboten. I've updated the examples above to highlight the first point.

+3  A: 

One possible workaround is to avoid the compiler calling the OtherClass constructor at all, and to call it on your own using placement new to initialize it whichever way you need. Example:

  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();
    }
  };

To cater for possible alignment requirements of the OtherClass, you may need to use __declspec(align(x)) if working in VisualC++, or to use a type other than char like:

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

... where Type is int, double, long long, or whatever describes the alignment requirements.

Suma
Don't forget to add "#include <new>" for placement new :)
Drealmer
I will have to check the performance of placement new on my platform, but otherwise this looks like a working solution, although it might not be understandable to my colleagues. :-)
TomiJ
I've selected this answer, although I'll probably keep my own version in actual use for now, as I think it's more readable and faster, though it does use extra memory for the convenience pointer array.
TomiJ
There should really be no performance problem with placement new, it is merely a syntax to call a constructor on some random block of memory. I would be suprised if it compiled to something other than just a straight call to the constructor, at least in release.
Simon Buchan
I made a quick trial changing one class to use placement new, and it did have a negative performance impact: the Foo constructor (which had 10 OtherClass instances in 4 arrays) is roughly 50 % slower now (up from 800 clock cycles to 1200, using `armcc -O3 -Otime`). This is mostly due to calling new.
TomiJ
+1  A: 

What data members are in OtherClass? Will value-initialization be enough for that class?

If value-initialization is enough, then you can value-initialize an array in the member initialization list:

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

private:
  int m_a[3];
};

If your array element types are class types, then the default constructor will be called.

EDIT: Just to clarify the comment from Drealmer.

Where the element type is non-POD, then it should have an "accessible default constructor" (as was stated above). If the compiler cannot call the default constructor, then this solution will not work.

The following example, would not work with this approach:

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];
};
Richard Corden
It won't work with an array of a type with no default constructor, but nice trick anyway.
Drealmer
A: 

Array members are not initialized by default. So you could use a static helper function that does the initialization, and store the result of the helper function in a member.

#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;
}

Worth noting is that in the next standard, they're going to support array initializers.

xtofl
Still the root of the problem remains, you need a default constructor for the array element type.
Drealmer
A: 

Use inheritance for creating proxy object

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

class Foo {
private:
  ProxyOtherClass inst[3]; // Array size fixed and known ahead of time.
public:
  Foo(...) {}
};
I might have read the reply too quickly, but I don't think that'll work since each OtherClass instance needs different parameters to the constructor.
TomiJ
A: 

And what about using array of pointers instead of array of objects? For example:

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;   
    }

};
foxthrot
Operator new (when used as above) will allocate the new objects on the heap, which was not allowed.
TomiJ
A: 

You say "Anything that can be initialized using only the initialization list is in our application far preferable to using the constructor, as that data can be allocated and initialized by the compiler and linker, and every CPU clock cycle counts".

So, don't use constructors. That is, don't use conventional "instances". Declare everything statically. When you need a new "instance", create a new static declaration, potentially outside of any classes. Use structs with public members if you have to. Use C if you have to.

You answered your own question. Constructors and destructors are only useful in environments with a lot of allocation and deallocation. What good is destruction if the goal is for as much data as possible to be allocated statically, and so what good is construction without destruction? To hell with both of them.

Matthew
A: 

One method I typically use to make a class member "appear" to be on the stack (although actually stored on the heap):

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]; }
};
To clients of your class, array appears to be of type "int const [3]". Combine this code with placement new and you can also truly initialize the values at your discretion using any constructor you desire. Hope this helps.

Jeff G