views:

218

answers:

3

I am trying to create an array of class objects taking an integer argument. I cannot see what is wrong with this simple little code. Could someone help?

#include <fstream>
#include <iostream>
using namespace std;

typedef class Object
{
 int var;
public:
 Object(const int& varin) : var(varin) {}
} Object;

int main (int argc, char * const argv[])
{
 for(int i = 0; i < 10; i++)
 {
  Object o(i)[100];
 }

 return 0;
}
+7  A: 

In C++ you don't need typedefs for classes and structs. So:

class Object
{
 int var;
public:
 Object(const int& varin) : var(varin) {}
};

Also, descriptive names are always preferrable, Object is much abused.

int main (int argc, char * const argv[])
{
 int var = 1;

 Object obj_array[10]; // would work if Object has a trivial ctor

 return 0;
}

Otherwise, in your case:

int main (int argc, char * const argv[])
{
 int var = 1;

 Object init(var);
 Object obj_array[10] = { var, ..., var }; // initialize manually

 return 0;
}

Though, really you should look for vector

#include <vector>
int main (int argc, char * const argv[])
{
 int var = 1;

 vector<Object> obj_vector(10, var); // initialize 10 objects with var value

 return 0;
}
dirkgently
+4  A: 

dirkgently's rundown is fairly accurate representation of arrays of items in C++, but where he is initializing all the items in the array with the same value it looks like you are trying to initialize each with a distinct value.

To answer your question, creating an array of objects that take an int constructor parameter. You can't, objects are created when the array is allocated and in the absence of a trivial constructor your compiler will complain. You can however initialize an array of pointers to your object but you really get a lot more flexibility with a vector so my following examples will use std::vector.

You will need to initialize each of the object separately if you want each Object to have a distinct value, you can do this one of two ways; on the stack, or on the heap. Lets look at on-the-stack first.

Any constructor that take a single argument and is not marked as explicit can be used as an implicit constructor. This means that any place where an object of that type is expected you can instead use an instance of the single parameter type. In this example we create a vector of your Object class and add 100 Objects to it (push_back adds items to a vector), we pass an integer into push_back which implicitly creates an Object passing in the integer.

#include <vector>
int main() {
    std::vector<Object> v;

    for(int i = 0; i < 100; i++) {
        v.push_back(i);
    }
}

Or to be explicit about it:

#include <vector>
int main() {
    std::vector<Object> v;

    for(int i = 0; i < 100; i++) {
        v.push_back(Object(i));
    }
}

In these examples, all of the Object objects are allocated on the stack in the scope of the for loop, so a copy happens when the object is pushed into the vector. Copying a large number of objects can cause some performance issues especially if your object is expensive to copy.

One way to get around this performance issue is to allocate the objects on the heap and store pointers to the objects in your vector:

#include <vector>
int main() {
    std::vector<Object*> v;

    for(int i = 0; i < 100; i++) {
        v.push_back(new Object(i));
    }

    for(int i = 0; i < 100; i++) {
        delete v[i];
    }
}

Since our objects were created on the heap we need to make sure that we delete them to call their deconstructor and, free their memory, this code does that in the second loop.

Manually calling delete has it's own caveats, if you pass these pointers to other code you can quickly loose track of who owns the pointers and who should delete them. An easier way to solve this problem is to use a smart pointer to track the lifetime of the pointer, see either boost::shared_ptr or tr1::shared_ptr which are reference-counted pointers :

#include <vector>
int main() {
    std::vector<shared_ptr<Object> > v;

    for(int i = 0; i < 100; i++) {
        Object* o = new Object(i);
        v.push_back(shared_ptr<Object>(o));
    }
}

You'll notice that the shared_ptr constructor is explicit, this is done intentionally to make sure that the developer is intentionally stuffing their pointer into the shared pointer. When all references to an object are released the object will automatically be deleted by the shared_ptr, freeing us of the need to worry about it's lifetime.

joshperry
Thanks for the follow up. This was also a great help!
Patrick Hogan
A: 

If you want to stick to arrays, then you must either initialize manually or use the default constructor. However, you can get some control by creating a constructor with a default argument. This will be treated as a default constructor by the compiler. For example, the following code prints out the numbers 0, ..., 9 in order. (However, I'm not sure that the standard dictates that the objects in the array must be constructed in order. It might be implementation dependent, in which case the numbers may appear in arbitrary order.)

#include <iostream>

using namespace std;

struct A {
   int _val;

   A(int val = initializer()) : _val(val) {}

   static int initializer() { static int v = 0;  return v++; }
};

int main()
{
   A a[10];
   for(int i = 0; i < 10; i++)
      cout << a[i]._val << endl;
}
Ari