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?
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} {}
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 of Object 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.
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.
Related
I have two classes, Base and Derived. Derived constructs Base using its own member object, which inherits from Base::BaseChild.
struct Base
{
struct BaseChild
{
int x = 5;
};
Base(BaseChild& c): baseData(c), constantVar(c.x)
{
assert(constantVar == 5);
}
int getX() const {return baseData.x;}
private:
const int constantVar;
BaseChild& baseData;
};
struct Derived: public Base
{
struct DerivedChild: public BaseChild
{
double y = 4.0;
};
Derived(): Base(data) {}
private:
DerivedChild data;
};
Derived myObject;
assert(myObject.getX() == 5);
Reasoning: I do this in this way because everything seems pretty encapsulated for my case, where I need to send Childs to swap their content (vector, shared_ptr, unique_ptr) with other Childs, keeping child memory address, and I still can access to Child object from base class without the need of a virtual function, which was killing my app performance.
Question: I've read another post like this one, where it states initialization of a Derived member before the Base isn't possible. So the constantVar assert would always fail. However getX() works fine, after the constructor, and I'm interested in these functions which are called once the constructor ends. Is this safe? Or is there any hidden danger here?
The base class Base of Derived is constructed before the member data.
As a result data will not be initialized when you pass a reference to it to Base's constructor. The initialization will happen after that constructor call.
You are however trying to read the member x of data in Base's constructor. At this point data's lifetime has not started yet and accessing the value of a non-static data member of an object outside its lifetime causes undefined behavior.
Whether or not the assertions succeed isn't significant. Undefined behavior allows for either outcome.
The situation would be potentially different (although technically not rules in the standard) if you were not trying to access the value of data inside Base's constructor, but only storing the reference to it.
I see some strange code in our project like follows.I test it and get the right answer.But I think it is illegal,Can anyone explain this to me?
class Member
{
public:
Member():
a(0),b(1)
{}
int a;
int b;
};
// contains `Member` as its first member
class Container
{
public:
Container():
c(0),d(0)
{}
Member getMemb(){return fooObject;}
Member fooObject;
int c;
int d;
};
and how we use it:
int main()
{
auto ctain = new Container;
auto meb = (Member *)ctain; // here! I think this is illegal
cout << "a is " << meb->a << ", b is" << meb->b << endl;
return 0;
}
but I get the right answer, a is 0 and b is 1.Is this just a coincidence?I also noted that if fooObject is not the first member, I will get a wrong answser.
The snippet is legal. The C style cast (Member*) here is effectively a reinterpret_cast. From [basic.compound]
Two objects a and b are pointer-interconvertible if:
they are the same object, or
one is a union object and the other is a non-static data member of that object, or
one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, the first base class subobject of that object, or [...]
If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_cast.
Special care should be taken to make sure it is indeed a standard layout type, possibly with a static_assert(std::is_standard_layout_v<Container>)
On the other hand, you could sidestep this entire fiasco if you just wrote auto meb = &ctain.fooObject;
It's not exactly coincidence, it happens that your fooObject is the first member of your Container class, so the beginning of it will rest at the same starting address as the Container object. If you do:
size_t s = offsetof(Container, Container::fooObject);
It will tell that your fooObject offset will be 0, which start where your Container object start in terms of memory, so when you cast to a Member pointer it's pointing to the correct address. But for instance in other cases you would be in big trouble for sure:
class Container
{
public:
Container() : c(0),d(0) {}
Member getMemb(){return fooObject;}
int c; // fooObject isn't first member
Member fooObject;
int d;
};
Or was a virtual class, because virtual classes store an pointer for lookup into a table.
class Container
{
public:
Container() : c(0),d(0) {}
virtual ~Container() {} // Container is virtual, and has a vtable pointer
// Meaning fooObject's offset into this class
// most likely isn't 0
Member getMemb(){return fooObject;}
Member fooObject;
int c;
int d;
};
Someone else will have to tell you whether this cast is legal even in your example, because I'm not sure.
C++ Standard in part 12.2 Class members:
If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. Otherwise, its address is the same as the address of its first base class subobject (if any).
Both your classes have standard-layout. So, observed behaviour agrees with Standard.
But the cast auto meb = (Member *)ctain; breaks strict aliasing rule.
Is it safe to pass pointers to data members to base class constructor? i.e., is the memory layout of the derived class data members, at least, already set before the base class constructor is called, even if the data members are not yet initialized?
Obviously, dereferencing the pointers must only be done after construction has completed, and the objects pointed to are already valid. But the question is if it's guaranteed that the pointers received by the base class constructor would actually still point to their objects once construction of the derived object has completed.
The motivation is providing some functionality in the base class, for example iterating over the pointers-to-objects provided at construction and do something for each at a later time.
It's possible to just provide a setter accessible to the derived class, but I'm curious if it's also safe to provide the pointers at construction time.
Example:
#include <iostream>
#include <utility>
#include <vector>
struct Base {
Base(std::initializer_list<int*> ptrs = {}) : ptrs_(ptrs) {}
std::vector<int*> ptrs_;
};
struct Derived : public Base {
Derived() : Base{{&a_,&b_,&c_}} {}
int a_=1, b_=2, c_=3;
};
int main()
{
Derived obj;
for (auto* ptr : obj.ptrs_) { std::cout << *ptr << '\n'; }
}
https://wandbox.org/permlink/rDJw0UU8KcWckLlo
Your code is fine. But note that the behaviour on dereferencing the pointers in the base class constructor would be undefined.
You are allowed to pass a pointer or reference to a member variable to a base class constructor, but you must not actually access the object in that base class constructor.
I see some strange code in our project like follows.I test it and get the right answer.But I think it is illegal,Can anyone explain this to me?
class Member
{
public:
Member():
a(0),b(1)
{}
int a;
int b;
};
// contains `Member` as its first member
class Container
{
public:
Container():
c(0),d(0)
{}
Member getMemb(){return fooObject;}
Member fooObject;
int c;
int d;
};
and how we use it:
int main()
{
auto ctain = new Container;
auto meb = (Member *)ctain; // here! I think this is illegal
cout << "a is " << meb->a << ", b is" << meb->b << endl;
return 0;
}
but I get the right answer, a is 0 and b is 1.Is this just a coincidence?I also noted that if fooObject is not the first member, I will get a wrong answser.
The snippet is legal. The C style cast (Member*) here is effectively a reinterpret_cast. From [basic.compound]
Two objects a and b are pointer-interconvertible if:
they are the same object, or
one is a union object and the other is a non-static data member of that object, or
one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, the first base class subobject of that object, or [...]
If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_cast.
Special care should be taken to make sure it is indeed a standard layout type, possibly with a static_assert(std::is_standard_layout_v<Container>)
On the other hand, you could sidestep this entire fiasco if you just wrote auto meb = &ctain.fooObject;
It's not exactly coincidence, it happens that your fooObject is the first member of your Container class, so the beginning of it will rest at the same starting address as the Container object. If you do:
size_t s = offsetof(Container, Container::fooObject);
It will tell that your fooObject offset will be 0, which start where your Container object start in terms of memory, so when you cast to a Member pointer it's pointing to the correct address. But for instance in other cases you would be in big trouble for sure:
class Container
{
public:
Container() : c(0),d(0) {}
Member getMemb(){return fooObject;}
int c; // fooObject isn't first member
Member fooObject;
int d;
};
Or was a virtual class, because virtual classes store an pointer for lookup into a table.
class Container
{
public:
Container() : c(0),d(0) {}
virtual ~Container() {} // Container is virtual, and has a vtable pointer
// Meaning fooObject's offset into this class
// most likely isn't 0
Member getMemb(){return fooObject;}
Member fooObject;
int c;
int d;
};
Someone else will have to tell you whether this cast is legal even in your example, because I'm not sure.
C++ Standard in part 12.2 Class members:
If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. Otherwise, its address is the same as the address of its first base class subobject (if any).
Both your classes have standard-layout. So, observed behaviour agrees with Standard.
But the cast auto meb = (Member *)ctain; breaks strict aliasing rule.
I have two classes where one is a base class containing a pointer to a member object on a derived class.
Like this:
class Bar { };
class Foo : Bar { };
class A
{
public:
A(Foo *foo) { this->foo = foo };
private:
Foo *foo;
}
class B : public A
{
public:
B() : A(&bar) { };
private:
Bar bar;
}
My question is: is B.bar guaranteed to be allocated before being passed as an initialisation parameter to the constructor of A?
Put another way: if I create an instance of B is B->foo guaranteed to be a valid pointer to an instance of a Bar?
The base subobject is constructed before the member objects. Pointers and references to all the members are valid, but you must not access the actual objects until they are constructed.
Keep in mind that while the particular example works (object is allocated so taking addresses of its members works), it is not a universally valid pattern (which I just learned hard way).
There is implicit cast B* to A*, which in this case is trivial, in case of having virtual methods and more complex objects can introduce offset (it can be executed on uninitialized memory), and in case of virtual inheritance would cause SegFault - if B inherited A virtually and as implementation detail B contained pointer to A and allocated it dynamically).