views:

475

answers:

7

(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) {}   };
+1  A: 

The compiler can determine weather you are calling the constructor of a base class or weather you are making an initialization.

Example 1:

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

The compiler can see that the name you are supplying belongs to a base class. It will therefore call the corresponding constructor in the base class.

Example 2:

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

The compiler can see you have a member variable called constant_member as part of your class, therefore it will initialize it with the value supplied.

You can initialize members and call base class constructors in the same initialization list (thats what the function-declaration syntax in the constructor is -- a initialization list).

Hassan Syed
+3  A: 

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.

Marcin Gil
+1  A: 

In your constructor, you can explicity call constructors for your member variables.

class FileOpener
{
public:
  // Note: no FileOpener() constructor
  FileOpener( string path ){ //Opens a file }
};
class A
{
public:
  A():b("../Path/To/File.txt"){}
  FileOpener b;
};

This is essential when your member variables don't have default constructors.

Similarly, you can explicitly call the constructor for your parent class when its default constructor won't do or doesn't exist.

class F
{
public:
  // Note: No default constructor again.
  F( int arg ){ var = arg;}
private:
  int var;
};
class D : public F
{
  D(){} //Compiler error! Constructors try to use the parent's default C 
        // constructor by default.
  D( int arg ):C(arg){} //This works!
};

Anyway, calling a constructor explicitly like this is called an initializer.

Edit: Be sure to initialize members in the order you declared them in your header or your code will have warnings at compile time.

thebretness
Re your special note, you can't get this wrong, the order in which the code in an initializer list is determined by the declarations/inheritance chain. Saying Base(), member() is no different than member(), Base(). In both cases Base() willbe run first.
Logan Capaldo
Ah, right. Update the note Re: attribute declaration order. I tend to work with -Werror turned on, so I'm pretty anal about initialization orders. Does putting the base initializer after the member initializers cause a warning?
thebretness
A: 

In constructor initializer lists you write data_member(val) in order to initialize data_member with val. Note that val may be an expression, even one that can only be evaluated at runtime. If the data member is an object, it's constructor will be called with that value. Furthermore, if it has a constructor expecting several argumets, you can pass them all, just like in a function call, e.g., data_member(i, j, k). Now, for the purpose of such initializations you should think of the base class part of the object as a data member whose name is simply the base class' names. Hence MyBase(val) or MyBase(i, ,j ,k). The base class's constructor will be called. Multiple inheritence works in the same way. Just initialize whichever base classes you want as separate items on the list: MyBase1(x), MyBase2(y). Base classes whose constructors you don't call explicitly will be initialized by their default constructors, if these exist. If they don't, the code won't compile unless you initialize explicitly.

Ari
+3  A: 

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 *
};
David Rodríguez - dribeas
A side-comment, but, why do people keep using `struct`s in their examples? I realize that the C++ implementation is the same as for `class`, but… I understood it to be bad practice to use `struct`s in C++ for, well, anything, since `class`es were ‘superior’ (i.e. more powerful).
elliottcable
BTW: Don’t get me wrong, I’m a huge fan of structs, coming from ANSI C. But I like to learn and use the standard style of the language I’m *in*… if `class`es are the ‘home style,’ I use that instead. But since all of you guys are using `struct`s in your examples, I’m not so sure I have grasped the ‘home style’s as well as I thought I had…
elliottcable
It is a little more concise. You do not need to add the extra `public:` in each and every classes. Then again, in real code I hardly ever use it for anything but small functors. The same goes for the whole formatting... I would use extra line breaks here and there, but the answer is already long enough without the extra space.
David Rodríguez - dribeas
I thought the `public:` was unnecessary, isn’t it the default? i.e., I thought a `class` with absolutely no protection specifiers was identical to the same `class` defined with the `struct` keyword…
elliottcable
Also, your answer is great… I added an addendum to the above post, if you’ve the time, I’d really appreciate an answer to that as well.
elliottcable
`class X {` is equivalent to `struct X { private:`, and viceversa: `struct X {` is equivalent to `class X { public:`
David Rodríguez - dribeas
@ elliot - A Class's default access is private, while a struct's is public. That is really the only difference between them.
SP
Oh! Thanks again. +1’d, each of you. This entire post cleared up a *lot* of confusion!
elliottcable
What SP said is true, but also a structs default inheritance is public, while class default inheritance is private. struct X : A {} is the same as struct X : public A {} while class X : A {} is the same as class X : private A {}
mocj
Seems worthy to insist on the fact that the order in which you define the initialization-list does not matter: it's the class definition which controls the order of initialization. In case your own list is organized differently, good compilers raise a warning so that you can check.
Matthieu M.
A: 

The book is trying to explain C++ initialization lists. In general, an initialization list consists of constructor calls, both to parent class constructors and class property constructors.

An initialization list should consist of (in order):

  1. Base class constructors
  2. Class property constructors

First all base class constructors should be called. The order of base class constructor calls is defined by the compiler. As stated by the C++ FAQ:

[Base class constructors]...are executed in the order they appear in a depth-first left-to-right traversal of the graph of base classes, where left to right refer to the order of appearance of base class names.

Thus, the order of the base class constructors in the initialization list does not matter. In case a base class constructor is not explicitly listed in the intialization list, the default constructor will be called.

Following the base class constructor calls are the class property constructor calls. These look like function calls, but are in essence a valid way of initializing variables called constructor initialization. For example, the following piece of C++ code is perfectly valid:

int i(0);

Note that the order of the class properties in the initialization list should match the order of definition in the class header.

Finally it is worth mentioning that using initialization lists is good practice. It is more efficient than using assignments in the constructor body since it eliminates the initial construction of the class property with a default or undefined value, and it ensures a strict order of initialization.

Ton van den Heuvel
As *I* read that snippet from the FAQ, it seems to me that they will be traversed by the order they’re defined in the `class`’s original declaration, not based on the order of the *`initialization list`* declaration… i.e. `class foo : bar, gaz` with `foo() : gaz(), bar()` would be executed as `bar(), gaz()`… right? Or am I off-base?
elliottcable
You are right, I've edited my answer, thanks.
Ton van den Heuvel
A: 

C++ has very ambiguous syntax, so be ready to meet many similar syntax constructions with different symantics.

For example:

A a(b);

This could mean a creation of an object 'a' of class 'A' by calling its contructor 'A::A' with a parameter value 'b'. But also this can be a declaration of function 'a' having a formal parameter of type 'b' and returning a value of type 'A'.

For a "simple" grammar the compiler can be implemented as a pipeline containing almost independent modules: Lexer, Parser, Semantic Analyzer, etc. Usually, such languages are not only simple for deciding by compilers but also are easy for understanding by humans (programmers).

C++ has very complicated grammar (and semantics). Therefore, without semantic information, C++ parser cannot decide which grammar rule to apply. This leads to difficulties in designing and implementing a C++ compiler. In addition, C++ makes it problematic to understand the programs by programmers.

So the root of your problems in understanding the syntax is not in your head but in the grammar of C++.

The reasons above leads to recommendations not to use C++ (and other overcomplicated languages) for teaching programming to beginners. First, use a simple (but powerful enought) language for developyng programming skills, then move to mainstream languages.

Slava
No, that seems wrong. Function declarations can only appear outside functions… so how is that ambiguous?
elliottcable
Oh. Wait. I just realized… you can initialize values outside of functions. Okay, so, you’ve got me… how does that get resolved by the compiler? d-:
elliottcable
It has similar problems you have. :-)See my update above.
Slava