I have the following piece of code that is working but I don't understand why it works (inspired by a real life code base):
Base class definition:
class Pointers {
private:
int* Obj1;
double* Obj2;
public:
Pointers(int* Obj1_, double* Obj2_) : Obj1{Obj1_}, Obj2{Obj2_} {}
};
We now derive a class from our base class where we shadow the two pointers with an int
and a double
of the same name:
class Objects : public Pointers {
public:
int Obj1{69};
double Obj2{72};
Objects() : Pointers(&Obj1, &Obj2) {}
};
Within the constructor of Objects
we call the constructor of Pointers
and pass the adresses of Obj1
and Obj2
. This actually works: The (shadowed) pointers will point to Obj1 (69)
and Obj2 (72)
.
My question is: Why is this working? I thought that in the first step the base class members are constructed (which are the two pointers) and only after that the derived class members (the int
and double
with the same name) are constructed. How can we pass these objects adresses to the base class constructor in the first place?
CodePudding user response:
It works, because at the moment when you call your base class constructor, the derived class members
int Obj1{69};
double Obj2{72};
are not yet initialized, but their addresses are already known, and you are using their addresses to initialize pointers in the base class. Note that it has little to do with shadowing.
Of course if you try to print the pointed values in Pointers
constructor
Pointers(int* Obj1_, double* Obj2_) : Obj1{Obj1_}, Obj2{Obj2_} {
std::cout << "Obj1: " << *Obj1 << ", Obj2: " << *Obj2 << std::endl;
}
you will get some garbage (UB), because pointed to values are not yet initialized. To make the code more clear avoid member fields assignments in the class body, rather use the constructor member initializer list:
Objects() : Pointers(&Obj1, &Obj2), Obj1{69}, Obj2{72} {}
CodePudding user response:
Here:
class Objects : public Pointers {
public:
int Obj1{69};
double Obj2{72};
Objects() : Pointers(&Obj1, &Obj2) {}
};
Obj1
refers to Objects::Obj1
. If there is a same named member in Pointers
then it is shadowed by the member in Objects
. You need to qualify the name Pointers::Obj1
. You don't do that, so Obj1
refers to Objects::Obj1
. Nothing surprising here.
Then here:
class Pointers {
private:
int* Obj1;
double* Obj2;
public:
Pointers(int* Obj1_, double* Obj2_) : Obj1{Obj1_}, Obj2{Obj2_} {}
};
Nothing is shadowed, Obj1_
and Obj1
are distinct identifiers. The base class initializes its members with the pointers you pass to the constructor.
This seems to indicate that when the ctor of Pointers is called within the ctor of Objects it is already known at which adresses in memory the members of the derived class will be?
Yes. You can use addresses and references of members of objects that are under construction, before those members are initialized. As long as you do not dereference the pointer (or try to read the member's value via the reference) all is fine. For example the code would invoke undefined behavior, if in the base constructors body you would add some std::cout << *Obj1;
, because the int
pointed to by Obj
is not initialized at that point.
CodePudding user response:
The initialization of the class actually proceeds as follows:
- Determine the memory that's used to construct the object (e.g. stack location or use of the result of the
new
operator). - Enter call of
Object::Object()
Pointers::Pointers(int* Obj1_, double* Obj2_)
gets called. This happens before any initializers for the members ofObject
are executed. The memory locations are already available though (see step 1.), so the address-of operator can be used.- Initialization of
Pointers
part of the object completes - The member variables of
Object
are initialized.
Note simply that simply passing the address of member variables can be done, but you need to make sure the pointers are not dereferenced in the base class constructor, since this would result in undefined behaviour.