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

(I'm sorry, if this was set earlier, the search function seems to be broken: the results pane is completely empty, although it says there are several pages of results ... in Chrome, FireFox and Safari)

So, I'm just learning C ++ ... and the book Im going through does a really bad job of explaining constructors in a way that I can understand them. Ive pretty much left everything else, but I can't figure out how the syntax for constructors works.

For example, I was told that the following would cause the constructor to invoke the assigned constructor of the superclass:

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

On the other hand, the same syntax was used later in the book when describing the initialization of 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 rv signature(param) : something_else(what); syntax means rv signature(param) : something_else(what); ? I can’t understand what is something_else(what) , relative to the code around it. It seems to have several meanings; I am sure that there must be some basic element of the language to which it corresponds, I simply cannot understand that.

Edit: Also, I have to mention that it is very confusing that what in the previous example is sometimes a list of parameters (so something_else(what) looks like a function signature) ... and sometimes a constant expression (so something_else(what) looks like function call).

Now, moving on: how about multiple inheritance and constructors? How can I indicate which constructors from which the parent classes are called ... and which of them are called by default? Im 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 finding these topics would be greatly appreciated; I do not like this feeling that Im do not understand something basic. I don’t like it at all.

Edit 2: Good, the answers below are very helpful at the moment. They raise yet another part of this question: How do the arguments of the base constructor-call classes in the "initialization lists" relate to the constructor you define? Do they need to comply ... do they need defaults? How much should they match? In other words, which of the following is 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) {} }; 
+4
source share
7 answers

Constructor definition syntax:

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

Where the "initialization list" is a list of calls invoked by commas for 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 it should be preferable to 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 determined in the class declaration: the base in the order in which they are declared, the attributes of the participant in the order in which they are declared. In the above example, before executing f() in the constructor body, the class initializes its base classes and attributes in the following sequence:

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

When you drop virtual inheritance, the virtual base is initialized in the most derived class of the virtual inheritance hierarchy, and as such it can (or should be, if there is no default constructor) appears in this initialization list. In this case, the virtual database will be initialized immediately before the first subobject, which actually inherits from this database.

 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 }; 

In editor 2

I think you do not read the details in the answers. Elements in the initialization list are constructor calls, not declarations. The compiler will apply the usual conversion rules for the call, if necessary.

 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 * }; 
+5
source

This idiom is called an initialization list .

Basically with every element that you call the 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 {}; }; 

In (1), you call the constructor of the base class, which takes an int as a parameter or can perform the corresponding conversion.

In (2), you call the constructor of the base class, which takes char as a parameter

In (3), you call the “constructor” to initialize the int , which in this case is a simple assignment

In (4), you call the constructor std::string(const char*) .

+3
source

The compiler can determine the weather you are calling the base class constructor or the weather you are doing 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 supply refers to the base class. Therefore, it will 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 that you have a member variable called constant_member as part of your class, so it initializes it with the supplied value.

You can initialize elements and call base class constructors in the same initialization list (this is what the function declaration syntax in the constructor is the initialization list).

+1
source

In your constructor, you can build call constructors for 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 important when member variables do not have default constructors.

Similarly, you can explicitly call the constructor for the parent class if its default constructor does not execute or does not 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 default C // constructor by default. D( int arg ):C(arg){} //This works! }; 

In any case, a constructor call that is explicitly similar to this is called an initializer.

Edit: Remember to initialize the elements in the order in which you declared them in your header, or your code will have warnings at compile time.

+1
source

In constructor initializer lists, you write data_member(val) to initialize data_member with val . Note that val can be an expression that can only be evaluated at run time. If the data item is an object, its constructor will be called with this value. Moreover, if it has a constructor that expects several arguments, you can pass them to everyone, as in a function call, for example, data_member(i, j, k) . Now, for initialization purposes, you should think of the part of the base class of the object as a data member whose name is simply the names of the base class. Therefore, MyBase(val) or MyBase(i, ,j ,k) . The base class constructor will be called. Multiple inheritance works the same way. Just initialize the required base class as a separate element in the list: MyBase1(x), MyBase2(y) . Base classes whose constructors you do not explicitly call will be initialized by default constructors, if they exist. If they do not, the code will not compile unless you explicitly initialize.

0
source

The book attempts to explain C ++ initialization lists. In general, an initialization list consists of designer calls, both for the constructors of the parent class and for the constructors of the properties of the class.

The initialization list should consist of (in order):

  • Base class constructors
  • Class Property Constructors

First you need to call all the constructors of the base class. The order of calls to the base class constructor is determined by the compiler. As pointed out by C ++ FAQs :

[Constructors of the base class] ... are executed in the order in which they appear in the intersection of depth from the first glance, from left to right, the graph of the base classes, where the names of the base class refer to the order from the left to the right.

Thus, the order of the base class constructors in the initialization list does not matter. If the constructor of the base class is not explicitly specified in the initialization list, the default constructor will be called.

Following the calls to the constructor of the base class, these are the calls to the constructor of the class properties. They look like function calls, but in essence they are a valid way to initialize variables called constructor initialization. For example, the following C ++ code snippet is valid:

 int i(0); 

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

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

0
source

C ++ has a strong ambiguous syntax, so be prepared to come across many similar syntax constructs with different symmatics.

For instance:

 A a(b); 

This may mean creating an object 'a' of class 'A' by calling its contructor 'A :: A' with the value of the parameter 'b'. But it can also be a function declaration of '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. As a rule, such languages ​​are not only simple for compilers to solve, but also easily understood by people (programmers).

C ++ has a very complex grammar (and semantics). Therefore, without semantic information, the C ++ analyzer cannot decide which grammar rule to apply. This leads to difficulties in developing and implementing the C ++ compiler. In addition, C ++ makes it difficult for programmers to understand programs.

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

The above reasons lead to recommendations not to use C ++ (and other complex languages) to teach programmers newbies. First, use a simple (but powerful enough) language to develop programming skills, and then go to the main languages.

0
source

Source: https://habr.com/ru/post/1302122/


All Articles