Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Some basic questions on constructors (and multiple-inheritance) in C++?

(I’m sorry if this has been asked before; the search feature seems to be broken: the results area is completely blank, even though it says there are a few pages of results… in Chrome, FireFox, and Safari)

So, I’m just learning C++… and the book I’m moving through is doing a really bad job of explaining constructors in a way that I can grasp them. I’ve pretty much grokked everything else so far, but I can’t figure out how the syntax for constructors actually works.

For instance, I am told that the following will cause the constructor to call the designated superclass’s constructor:

class something : something_else {
  something(int foo, double bar) : something_else(int foo) {}
};

On the other hand, that same syntax was utilized later on in the book, when describing how to initialize const members:

class something : something_else {
private:  const int constant_member;
public:   something(int foo, double bar) : constant_member(42) {}
};

So… uh… what the hell is going on there? What does the syntax rv signature(param) : something_else(what); actually mean? I can’t figure out what that something_else(what) is, with relation to the code around it. It seems to take on multiple meanings; I’m sure there must be some underlying element of the language that it corresponds to, I just can’t figure out what.

Edit: Also, I should mention, it’s very confusing that the what in the previous example is sometimes a parameter list (so something_else(what) looks like a function signature)… and sometimes a constant-value expression (so something_else(what) looks like a function call).

Now, moving on: What about multiple-inheritance and constructors? How can I specify what constructors from which parent classes are called… and which ones are called by default? I’m aware that, by default, the following two are the same… but I’m not sure what the equivalent is when multiple-inheritance is involved:

class something : something_else {
//something(int foo, double bar) : something_else() {}
  something(int foo, double bar) {}
};

Any help in grokking these topics would be very appreciated; I don’t like this feeling that I’m failing to understand something basic. I don’t like it at all.

Edit 2: Okay, the answers below as of now are all really helpful. They raise one more portion of this question though: How do the arguments of base-class-constructor-calls in ‘initialization lists’ relate to the constructor you’re defining? Do they have to match… do there have to be defaults? How much do they have to match? In other words, which of the following are illegal:

class something_else {
  something_else(int foo, double bar = 0.0) {}
  something_else(double gaz) {}
};


class something : something_else {
  something(int foo, double bar)  : something_else(int foo, double bar) {}   };
class something : something_else {
  something(int foo)              : something_else(int foo, double bar) {}   };
class something : something_else {
  something(double bar, int foo)  : something_else(double gaz) {}   };
like image 547
ELLIOTTCABLE Avatar asked Dec 30 '22 01:12

ELLIOTTCABLE


2 Answers

The syntax for a constructor definition is:

Type( parameter-list ) : initialization-list 
{
   constructor-body
};

Where the 'initialization-list' is a comma separated list of calls to constructors for the bases and/or member attributes. It is required to initialize any subobject (base or member) for which there is no default constructor, constant subobjects and reference attributes, and should be preferred over assignment in the constructor block in all other cases.

struct base {
   base( int ) {};
};
struct base2 {
   base2( int ) {};
};
struct type : base, base2
{
   type( int x ) 
      : member2(x), 
        base2(5), 
        base(1), 
        member1(x*2) 
   { f(); }
   int member1;
   int member2;
};

The order in which the initialization list is executed is defined in the class declaration: bases in the order in which they are declared, member attributes in the order in which they are declared. In the example above before executing f() in the constructor body the class will initialize its base classes and attributes in the following sequence:

  1. call base(int) constructor with parameter 1
  2. call base2(int) constructor with parameter 5
  3. initialize member1 with value x*2
  4. initialize member2 with value x

When you throw in virtual inheritance, the virtual base is initialized in the most derived class of the virtual inheritance hierachy, and as such it can (or must if there is no default constructor) appear in that initialization list. In that case, the virtual base will be initialized right before the first subobject that inherits virtually from that base.

class unrelated {};
class base {};
class vd1 : virtual base {};
class vd2 : virtual base {};
struct derived : unrelated, vd1, vd2 {
   derived() : unrelated(), base(), vd1(), vd2() {} // in actual order
};

On Edit 2

I think you are not reading the details in the answers. The elements in the initialization list are constructor calls, not declarations. The compiler will apply the usual conversion rules for the call if appropriate.

struct base {
   base( int x, double y );
   explicit base( char x );
};
struct derived : base {
   derived() : base( 5, 1.3 ) {}
   derived( int x ) : base( x, x ) {} 
      // will convert x into a double and call base(int,double)
   derived( double d ) : base( 5 ) {} 
      // will convert 5 to char and call base(char)
// derived( base b ) {} // error, base has no default constructor
// derived( base b, int x ) : base( "Hi" ) {} 
      // error, no constructor of base takes a const char *
};
like image 138
David Rodríguez - dribeas Avatar answered Jan 14 '23 08:01

David Rodríguez - dribeas


This idiom is called initialization list.

Basically with each item you call a constructor:

class C: public A, public B {
    int a;
    std::string str;

public:
    C(): 
        A(5),            // 1
        B('c'),          // 2
        a(5),            // 3
        str("string")    // 4
    {};
};

At (1) you call base class constructor that takes int as a parameter or can perform appropriate conversion.

At (2) you call base class constructor that takes char as a parameter

At (3) you call "constructor" to initialize an int which in this case is simple assignment

At (4) you call std::string(const char*) constructor.

like image 35
Marcin Gil Avatar answered Jan 14 '23 08:01

Marcin Gil