views:

372

answers:

6

Hi,

I'm sure this is a really simple question. The following code shows what I'm trying to do:

class MemberClass {
public:
    MemberClass(int abc){ }
};

class MyClass {
public:
    MemberClass m_class;
    MyClass(int xyz) {
        if(xyz == 42)
            m_class = MemberClass(12);
        else
            m_class = MemberClass(32);
    }
};

This doesn't compile, because m_class is being created with an empty constructor (which doesn't exist). What is the right way of doing this? My guess is using pointers and instantiating m_class using new, but I'm hoping there is an easier way.

Edit: I should have said earlier, but my actual problem has an additional complication: I need to call a method before initializing m_class, in order to set up the environment. So:

class MyClass {
public:
    MemberClass m_class;
    MyClass(int xyz) {
        do_something(); // this must happen before m_class is created
        if(xyz == 42)
            m_class = MemberClass(12);
        else
            m_class = MemberClass(32);
    }
};

Is it possible to achieve this with fancy initialization list tricks?

+5  A: 

Use the initializer list syntax:

class MyClass {
public:
    MemberClass m_class;
    MyClass(int xyz) : m_class(xyz == 42 ? MemberClass(12) : MemberClass(32)
                               /* see the comments, cleaner as xyz == 42 ? 12 : 32*/)
    { }
};

Probably cleaner with a factory:

MemberClass create_member(int x){
   if(xyz == 42)
     return MemberClass(12);
    // ...
}

//...
 MyClass(int xyz) : m_class(create_member(xyz))
Todd Gardner
You are creating an unnamed object, and then using the copy-ctor to initialize the member, rather than initializing it directly.
James Curran
Yeah, typing too fast for my own good. Personally, I'd still move the logic into MemberClass or a factory.
Todd Gardner
+4  A: 
 MyClass(int xyz) : m_class(xyz==42 ? 12 : 32) {}

To answer your revised question, that get a bit tricky. The simplest way would be to make m_class a pointer. If you really want it as a data member, then you have to get creative. Create a new class (it's best if it's defined internal to MyClass). Have it's ctor be the function that needs to be called. Include it first among the declarations of data members (this will make it the first instaniated).

class MyClass 
{
     class initer { public: initer() {
                    // this must happen before m_class is created
                    do_something();                        
                    }
                   }

    initer     dummy;
public:

    MemberClass m_class;
    MyClass(int xyz) : m_class(xyz==42? 12 : 43)
    {
        // dummy silently default ctor'ed before m_class.
    }
};
James Curran
+15  A: 

Use the conditional operator. If the expression is larger, use a function

class MyClass {
public:
    MemberClass m_class;
    MyClass(int xyz) : m_class(xyz == 42 ? 12 : 32) {

    }
};

class MyClass {
    static int classInit(int n) { ... }
public:
    MemberClass m_class;
    MyClass(int xyz) : m_class(classInit(xyz)) {

    }
};

To call a function before initializing m_class, you can place a struct before that member and leverage RAII

class MyClass {
    static int classInit(int n) { ... }
    struct EnvironmentInitializer {
        EnvironmentInitializer() {
            do_something();
        }
    } env_initializer;
public:
    MemberClass m_class;
    MyClass(int xyz) : m_class(classInit(xyz)) {

    }
};

This will call do_something() before initializing m_class. Note that you are not allowed to call non-static member functions of MyClass before the constructor initializer list has completed. The function would have to be a member of its base class and the base class' ctor must already be completed for that to work.

Also note that the function, of course, is always called, for each separate object created - not only for the first object created. If you want to do that, you could create a static variable within the initializer's constructor:

class MyClass {
    static int classInit(int n) { ... }
    struct EnvironmentInitializer {
        EnvironmentInitializer() {
            static int only_once = (do_something(), 0);
        }
    } env_initializer;
public:
    MemberClass m_class;
    MyClass(int xyz) : m_class(classInit(xyz)) {

    }
};

It's using the comma operator. Note that you can catch any exception thrown by do_something by using a function-try block

class MyClass {
    static int classInit(int n) { ... }
    struct EnvironmentInitializer {
        EnvironmentInitializer() {
            static int only_once = (do_something(), 0);
        }
    } env_initializer;
public:
    MemberClass m_class;
    MyClass(int xyz) try : m_class(classInit(xyz)) {

    } catch(...) { /* handle exception */ }
};

The do_something function will be called again next time, if it threw that exception that caused the MyClass object fail to be created. Hope this helps :)

Johannes Schaub - litb
Thanks! I have updated my question - I actually need to run a method before creating m_class. Is that possible? Perhaps I _could_ do it in "classInit", but that wouldn't be very elegant.
Pedro d'Aquino
Learned a lot, thanks! :-)
Pedro d'Aquino
When I saw the use of the comma operator, I knew the person answering this question was clever. When I saw the use of the function try block, I knew the person answering this question was litb. When I saw the person answering this question was litb... well I guess I already knew it was him :)
michalmocny
it also gets more complicated if your variable's constructor takes multiple arguments; then you will need one of these functions for each argument
newacct
A: 

Or:

class MemberClass {
public:
    MemberClass(int abc){ }
};

class MyClass {
public:
    MemberClass* m_class;
    MyClass(int xyz) {
        if(xyz == 42)
            m_class = new MemberClass(12);
        else
            m_class = new MemberClass(32);
    }
};

If you somehow still want to keep the same syntax. Member initalization is more efficient though.

Magnus Skog
+1  A: 

Try this:

class MemberClass
{
public:    
   MemberClass(int abc = 0){ }
};

This gives it a default value, and your default constructor.

Kieveli
Sometimes there's reasons not to have a default constructor.
David Thornley
You would have to have a default parameter that put it into some known zombie state. Not ideal but sometimes the only way.
Martin Beckett
Sometimes there's reasons to make the default constructor private, but it should always be there...
Kieveli
+1  A: 

To have the initialization happen after other stuff happens, you do indeed need to use pointers, something like this:

class MyClass {
public:
    MemberClass * m_pClass;
    MyClass(int xyz) {
        do_something(); // this must happen before m_class is created
        if(xyz == 42)
            m_pClass = new MemberClass(12);
        else
            m_pClass = new MemberClass(32);
    }
};

The only difference is that you'll need to access member variables as m_pClass->counter instead of m_class.counter, and delete m_pClass in the destructor.

Aric TenEyck
The alternative is to initialize Myclass into some dummy zombie state and re-init later once you know the value. But I would prefer pointers.
Martin Beckett