views:

456

answers:

11

I have a (for me) complex object with about 20 data member, many of which are pointer to other classes. So for the constructor, I have a big long, complex initialization list. The class also has a dozen different constructors, reflecting the various ways the class can be created. Most of these initialized items are unchanged between each of these different constructors.

My concern here is that I now have a large chuck of copied (or mostly copied) code which, if I need to add a new member to the class, may not make it into each of the constructor initialization lists.

class Object 
{
    Object();
    Object(const string &Name);
    Object (const string &Name, const string &path);
    Object (const string &Name, const bool loadMetadata);
    Object (const string &Name, const string &path, const bool loadMetadata);
} 

Object::Object() :
    name(),
    parent_index (0),
    rowData (new MemoryRow()),
    objectFile (),
    rows (new MemoryColumn (object_constants::RowName, OBJECTID, object_constants::ROWS_OID)),
    cols (new MemoryColumn (object_constants::ColName, OBJECTID, object_constants::COLS_OID)),
    objectName (new MemoryColumn(object_constants::ObjName, STRING, object_constants::short_name_len, object_constants::OBJECTNAME_OID)),
    parent     (new MemoryColumn(object_constants::ParentName, STRING, object_constants::long_name_len, object_constants::PARENT_OID)),
    parentIndex (new MemoryColumn(object_constants::ParentIndex, OBJECTID, object_constants::PARENTINDEX_OID)),
    childCount (new MemoryColumn (object_constants::ChildCount, INTEGER, object_constants::CHILD_COUNT_OID)),
    childList (new MemoryColumn (object_constants::ChildList, STRING, object_constants::long_name_len, object_constants::CHILD_OID)),
    columnNames (new MemoryColumn (object_constants::ColumnNames, STRING, object_constats::short_name_len, object_constants::COLUMN_NAME)),
    columnTypes (new MemoryColumn (object_constants::ColumnTypes, INTEGER, object_constants::COLUMN_TYPE)),
    columnSizes (new MemoryColumn (object_constants::ColumnSizes, INTEGER, object_constants::COLUMN_SIZE))
{}

Then repeat as above for the other constructors. Is there any smart way of using the default constructor for this, then modifying the results for the other constructors?

+4  A: 

Boost::Parameter makes it easy to implement Named Parameter Idiom. Check out this thread on SO. This may not be exactly what you need, but provides for some flexibility when you want to forward calls to the default ctor.

dirkgently
can you explain how the named parameter idiom is related?
The second link.
dirkgently
+4  A: 

Not to do with constructors, but why do you think you have to create all those sub-objects dynamically with new? This is not a good idea - you should avoid dynamic creation wherever possible. Don't make all those members pointers - make them actual objects.

anon
That works some of the time. What if the class then becomes very large, and mostly unused? What if it's necessary to copy class objects fairly frequently? What if different members wind up sharing subobjects?
David Thornley
Well to truly copy you wouldn't do a shallow copy anyway. If your class has this many objects there's a good chance you need to refactor.
GMan
+3  A: 

You can share their common code in a private init() member function.

Example:

class Object
{
 public:
   Object(const string &Name);
   Object(const string &Name, const string &path);
   ...
 private:
   void init();
 };

 Object::Object(const string &Name)
 {
   init();
   ...
 }

 Object::Object(const string &Name, const string &path)
 {
   init();
   ...
 }

 void Object::init()
 {
//intialization stuff
   ...
 }
aJ
This of course doesn't work if the members are const.
Eclipse
This means you are not initializing, you're post-initializing. This might be acceptable, but is usually slower, and, when you have members without a default ctor, impossible.
Agreed, its slow. But avoids dupilcation of code.
aJ
It will not work for references either. I would have loved to see C++ have a way to chain constructors to avoid this type of problem.
Martin York
I started to use this method, but all the c++ references I've found (books and web) all insist that using the initializer list was the way to go.
Thomas Jones-Low
Yes, initializer list should be preferred. But in this case I had to prefer init() to avoid the code duplication. Additional reference : http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.3
aJ
+4  A: 

Yes, it's possible.
For simplicity I'll pretend that the original code is:

class Foo {
public:
    Foo() : a(0), b(1), x() { }
    Foo(int x) : a(0), b(1), x(x) { }

    int get_a() const { return a; }
    int get_b() const { return b; }
    int get_x() const { return x; }
private:
    int a, b, x;
};

The refactored code, then, is:

class Foo {
public:
    Foo() : x() { }
    Foo(int x) : x(x) { }

    int get_a() const { return common.a; }
    int get_b() const { return common.b; }
    int get_x() const { return x; }
private:
    struct Common {
        Common() : a(0), b(1) { }
        int a, b;
    } common;
    int x;
};
Except that you've just made it impossible to refer to bar.a, it's now bar.sub.a.
David Thornley
Of course, but it would be encapsulated in an accessor like Foo::getA() so it's not a problem :)
I edited to make that a little more clear.
+1  A: 

First of all, you'll have memory leaks if you don't implement deletion of the allocated objects in the destructor. So you should define your destructor and delete the objects there.

If you really need to dynamically allocate the members (I don't recommended if this class owns the data member object), you can have a private method doing all the initialization and you can call that method from your constructors.

Cătălin Pitiș
all the values of new MemoryColum() are going into smart pointers. This will automatically delete them when Object is destroyed. In theory.
Thomas Jones-Low
A: 

Do you really need 5 different constructors?

Very often if you need convert constructors you don't also want a default constructor.

Your other constructors all take just different combination of the same thing. It might be a better idea to have just one constructor that takes all of the parameters, with an option for each parameter that indicates the desire to invoke some kind of default for that parameter.

For example:

class Object
{
  Object (const string *Name, // can be NULL
    const string *path, // can be NULL 
    const bool loadMetadata);

};
John Dibling
Taking NULL pointers-to-string would be very unexpected, but as a last resort it's workable, yes.
+1  A: 
Object (const string &Name = "", const string &path = "", const bool loadMetadata = false);

This won't solve all of your problems (in particular, there's no way to represent the constructor with Name and loadMetaData), but it will at least collapse some of the constructors into one.

David Thornley
+1  A: 

I'll preface this by saying that I (obviously) don't know the details of your system, or the constraints that led to your design decisions.

That being said, a good rule of thumb is that when a class starts getting unweildy - around the time when you start asking questions about how to handle it :) - it may be time to refactor that class into a couple of smaller subclasses.

Remember that a class is supposed to do one thing very well. If you start to have large classes that try to do too many things, you're drifting away from good OO design.

e.James
A: 

Just put the initializer list inside a MACRO and be done with it.

BigSandwich
+1  A: 

I would use different factory methods (static methods) that would return back a smart ptr to your class. The factory method names would also help document WHY you need all the different parameters.

chrish
+1, I think that's a good idea, but I don't see how it gets around the problems mentioned by Iraimbilanja -- namely, how to initialise const members and references. You still need a variety of constructors if you want to offer defaults on these.
j_random_hacker
+4  A: 

How about refactor the common fields into a base class. The default constructor for the base class would handle initialization for the plethora of default fields. Would look something like this:

class BaseClass {
    public:
    BaseClass();
};

class Object : public BaseClass
{
    Object();
    Object(const string &Name);
    Object (const string &Name, const string &path);
    Object (const string &Name, const bool loadMetadata);
    Object (const string &Name, const string &path, const bool loadMetadata);
};

BaseClass::BaseClass() :
    parent_index (0),
    rowData (new MemoryRow()),
    objectFile (),
    rows (new MemoryColumn (object_constants::RowName, OBJECTID, object_constants::ROWS_OID)),
    cols (new MemoryColumn (object_constants::ColName, OBJECTID, object_constants::COLS_OID)),
    objectName (new MemoryColumn(object_constants::ObjName, STRING, object_constants::short_name_len, object_constants::OBJECTNAME_OID)),
    parent     (new MemoryColumn(object_constants::ParentName, STRING, object_constants::long_name_len, object_constants::PARENT_OID)),
    parentIndex (new MemoryColumn(object_constants::ParentIndex, OBJECTID, object_constants::PARENTINDEX_OID)),
    childCount (new MemoryColumn (object_constants::ChildCount, INTEGER, object_constants::CHILD_COUNT_OID)),
    childList (new MemoryColumn (object_constants::ChildList, STRING, object_constants::long_name_len, object_constants::CHILD_OID)),
    columnNames (new MemoryColumn (object_constants::ColumnNames, STRING, object_constats::short_name_len, object_constants::COLUMN_NAME)),
    columnTypes (new MemoryColumn (object_constants::ColumnTypes, INTEGER, object_constants::COLUMN_TYPE)),
    columnSizes (new MemoryColumn (object_constants::ColumnSizes, INTEGER, object_constants::COLUMN_SIZE))
{}

Your Object constructors should look a little more manageable, now:

Object::Object() : BaseClass() {}
Object::Object (const string &Name): BaseClass(), name(Name) {}
Object::Object (const string &Name, const string &path): BaseClass(), name(Name), path_(path){}
Object::Object (const string &Name, const bool loadMetadata): BaseClass(), name(Name){}
Object::Object (const string &Name, const string &path, const bool loadMetadata): BaseClass(), path_(path) {}

Similar in nature to Iraimbilanja's answer, but avoids adding an inner-class for accessing data, which might impact a lot of existing code. If you've already got a class hierarchy, though, it may be difficult to factor it into a base class.

veefu
might want to use private inheritance because this is an implementation detail.