views:

488

answers:

8

I'm having a brain cramp... how do I initialize an array of objects properly in C++?

non-array example:

struct Foo { Foo(int x) { /* ... */  } };

struct Bar { 
     Foo foo;

     Bar() : foo(4) {}
};

array example:

struct Foo { Foo(int x) { /* ... */  } };

struct Baz { 
     Foo foo[3];

     // ??? I know the following syntax is wrong, but what's correct?
     Baz() : foo[0](4), foo[1](5), foo[2](6) {}
};

edit: Wild & crazy workaround ideas are appreciated, but they won't help me in my case. I'm working on an embedded processor where std::vector and other STL constructs are not available, and the obvious workaround is to make a default constructor and have an explicit init() method that can be called after construction-time, so that I don't have to use initializers at all. (This is one of those cases where I've gotten spoiled by Java's final keyword + flexibility with constructors.)

+11  A: 

There is no way. You need a default constructor for array members and it will be called, you can after do any initialization you want in the constructor.

AProgrammer
Unfortunately, you're right. +1 Note that C++1x' unified initialization syntax will allow you to do this.
sbi
@sbi: I still call it "C++0x". Of course, I also sometimes give my age as 38, and hope to retire at the age of 3E.
David Thornley
@David: I call it C++1x (hoping it will be released within the next 9.5 years).
sbi
maybe D will catch on by then....
Jason S
The committee continues to use C++0X. I though I had seen a rational for that but I'm currently unable to find it.
AProgrammer
Perhaps simply because using a new name would invalidate everything that has been written up to now ? Changing the name of something after it's become widespread is more annoying that anything (think of it as deadlinks all over the internet).
Matthieu M.
A: 

Only the default constructor can be called when creating objects in an array.

Robert
+4  A: 

Right now, you can't use the initializer list for array members. You're stuck doing it the hard way.

class Baz {
    Foo foo[3];

    Baz() {
        foo[0] = Foo(4);
        foo[1] = Foo(5);
        foo[2] = Foo(6);
    }
};

In C++0x you can write:

class Baz {
    Foo foo[3];

    Baz() : foo({4, 5, 6}) {}
};
Kristo
??? foo is a Foo, not an int... how can I do "foo[0] = 4"? (unless there's an assignment operator defined)
Jason S
@Jason: There, I fixed it.
sbi
A one argument constructor will be called for an int unless you declare the constructor explicit.
jmanning2k
interesting... I probably should have used something besides `int` then in my example, as it's too "easy" to deal with. :-)
Jason S
+1  A: 

In the specific case when the array is a data member of the class you can't initialize it in the current version of the language. There's no syntax for that. Either provide a default constructor for array elements or use std::vector.

A standalone array can be initialized with aggregate initializer

Foo foo[3] = { 4, 5, 6 };

but unfortunately there's no corresponding syntax for the constructor initializer list.

AndreyT
A: 

There is no array-construction syntax that ca be used in this context, at least not directly. You can accomplish what you're trying to accomplish by something along the lines of:

Bar::Bar()
{
    static const int inits [] = {4,5,6};
    static const size_t numInits = sizeof(inits)/sizeof(inits[0]);
    std::copy(&inits[0],&inits[numInits],foo);  // be careful that there are enough slots in foo
}

...but you'll need to give Foo a default constructor.

John Dibling
+1  A: 

This seems to work, but I'm not convinced it's right:

#include <iostream>

struct Foo { int x; Foo(int x): x(x) { } };

struct Baz { 
     Foo foo[3];

    static int bar[3];
     // Hmm...
     Baz() : foo(bar) {}
};

int Baz::bar[3] = {4, 5, 6};

int main() {
    Baz z;
    std::cout << z.foo[1].x << "\n";
}

Output:

$ make arrayinit -B CXXFLAGS=-pedantic && ./arrayinit
g++ -pedantic    arrayinit.cpp   -o arrayinit
5

Caveat emptor.

Edit: nope, Comeau rejects it.

Another edit: This is kind of cheating, it just pushes the member-by-member array initialization to a different place. So it still requires Foo to have a default constructor, but if you don't have std::vector then you can implement for yourself the absolute bare minimum you need:

#include <iostream>

struct Foo { 
    int x; 
    Foo(int x): x(x) { }; 
    Foo(){}
};

// very stripped-down replacement for vector
struct Three { 
    Foo data[3]; 
    Three(int d0, int d1, int d2) {
        data[0] = d0;
        data[1] = d1;
        data[2] = d2;
    }
    Foo &operator[](int idx) { return data[idx]; }
    const Foo &operator[](int idx) const { return data[idx]; }
};

struct Baz { 
    Three foo;

    static Three bar;
    // construct foo using the copy ctor of Three with bar as parameter.
    Baz() : foo(bar) {}
    // or get rid of "bar" entirely and do this
    Baz(bool) : foo(4,5,6) {}
};

Three Baz::bar(4,5,6);

int main() {
    Baz z;
    std::cout << z.foo[1].x << "\n";
}

z.foo isn't actually an array, but it looks about as much like one as a vector does. Adding begin() and end() functions to Three is trivial.

Steve Jessop
+1 for trying to make a workaround
Jason S
...and this gives me some ideas that might work for my situation more cleanly than what I have. thanks!
Jason S
A: 

Ideas from a twisted mind :

class mytwistedclass{
static std::vector<int> initVector;
mytwistedclass()
{
    //initialise with initVector[0] and then delete it :-)
}

};

now set this initVector to something u want to before u instantiate an object. Then your objects are initialized with your parameters.

the100rabh
+3  A: 

Unfortunately there is no way to initialize array members till C++0x.

You could use a std::vector and push_back the Foo instances in the constructor body.

You could give Foo a default constructor (might be private and making Baz a friend).

You could use an array object that is copyable (boost or std::tr1) and initialize from a static array:

#include <boost/array.hpp>

struct Baz {

    boost::array<Foo, 3> foo;
    static boost::array<Foo, 3> initFoo;
    Baz() : foo(initFoo)
    {

    }
};

boost::array<Foo, 3> Baz::initFoo = { 4, 5, 6 };
UncleBens
+1. Wondered why noone came up with this, until i saw your answer. `array` is trivial to implement, and it's neither wild nor crazy. You could write a function like `array<Foo, 3> create() { array<Foo, 3> a = { ... }; return a; }` to avoid the static variable, too.
Johannes Schaub - litb
Seems obvious to me too, even if the support for templates is weak on the target compiler (no `std::vector` seems fishy) a generation approach would work (preprocessor or script generating necessary classes).
Matthieu M.