#how on earth is this code working
125 messages · Page 1 of 1 (latest)
When your question is answered use !solved to mark the question as resolved.
Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.
some week ago i ask a question here that basically create something like this but with shared ptr and smart ptr
i just wanna understand how it by passing the base struct as pointer ill be able to pass its child struct
Pasting the code here for convenience.
#include <iostream>
#include <vector>
#include <string>
struct Base {
std::string data;
Base() = default;
virtual void do_something() = 0;
protected:
Base(std::string& a):data{a}{};
};
struct person : public Base {
person(std::string name):
Base(name){}
void do_something() {
std::cout << "im a person and ill do '" << data << "'" << std::endl;
}
};
struct dog : public Base {
dog(std::string name):
Base(name){}
void do_something() {
std::cout << data << "bark bark bark" << std::endl;
}
};
int main(){
std::vector<Base*> d{5};
d[0] = new person{"meu"};
d[1] = new dog{"mutt"};
for (int i{0}; i < 2; i++) {
d[i]->do_something();
}
return 0;
}
what part in particular is surprising to you here?
is it about how virtual functions work or how it interacts with the vector?
how is it possible that by passing the base struct as pointer allow me to pass its child struct
there is no passing of a child going on
line 33-34
we don't have line numbers
that's just converting e.g. the person* returned from new person("meu") to Base* implicitly
you can always convert a pointer to the derived class to a pointer to a (public and unambiguous) base class implicitly
otherwise inheritance would be pretty useless
if its Base* how does it able to acces functionality thats not from Base ?
because it calls a virtual function
that's what virtual functions do
a non-static member function with the same name and signature in a derived class overrides a virtual function in the base class
when you call the virtual base class function it will call the final overrider of the function, i.e. the most-derived override in the most-derived class of the object that the pointer points to
The usual recommendation to learn about how this is all implemented (typically) is "Inside the C++ Object Model" by S. Lippman.
It might not be entirely up-to-date, I am not sure. It is from 1996 so it will be talking about something slightly prior to the standardized C++98.
homie, beginners need a lot more detail to understand that
that's fine, they can look up what it means and ask follow-up questions
in fact the follow-up seems to suggest that they understand the semantics, but want to know about the implementation
so, first, we must observe the binary representation of your classes here, first assuming that do_something was not virtual
and observe how the pointer casts are actually working
let me draw you a diagram
They specifically asked for "in depth like on the assembly side".
@wanton junco
When virutal functions are not used,
#include <iostream>
#include <vector>
#include <string>
struct Base {
std::string data;
Base() = default;
//virtual void do_something() = 0;
void do_something()
{
}
protected:
Base(std::string& a):data{a}{};
};
struct person : public Base {
person(std::string name):
Base(name){}
void do_something() {
std::cout << "im a person and ill do '" << data << "'" << std::endl;
}
};
struct dog : public Base {
dog(std::string name):
Base(name){}
void do_something() {
std::cout << data << "bark bark bark" << std::endl;
}
};
int main(){
std::vector<Base*> d{5};
d[0] = new person{"meu"};
d[1] = new dog{"mutt"};
for (int i{0}; i < 2; i++) {
d[i]->do_something();
}
return 0;
}
the representation is:
this
in your main function, you have
d[0] = new person{"meu"};
this snippet here
the leftside expression is accessing the 0-th element from the vector, the returned type is
Base*&, a reference to a pointer to struct Base, or
using PtrType = Base*;
using RefType = PtrType&;
this
and
on the rightside expression you have a call to operator new for the class person, the return type is person*
in the declaration of your classes, we have public inheritance, not protected inheritance, not private inheritance
which means that, when a person* value is converted to a Base* variable, the conversion is well defined by the compiler, normally any implicit pointer conversion would instantly raise a compilation error,
done by, opening the person object, locating where in memory the raw Base object lives, all done by the compiler (here Base and person are both 32 bytes in size, so they are almost the same) and taking a pointer to that Base value.
Here due to coincidence, the first byte of the person object is matching the first byte of the Base object, thus the returned Base* pointer is pointing to the same memory address.
This implicit conversion from person* to Base* is done via static_cast<Base*>(new person{"meu"}); implicitly.
if person was inheriting from not one but two different structs, well then you would have two nested objects stored within the person struct, and the static_cast would return you two different pointers for that situation.
like:
struct F1
{
char val = 'A';
};
struct F2
{
char val = 'B';
};
struct person : F1, F2
{
};
int main()
{
person human;
F1* p1 = &human; // static_cast<F1*>(&human);
F2* p2 = &human; // static_cast<F2*>(&human);
//these two addresses are different
std::cout<<p1<<" "<<p2<<"\n";
}
the static_cast usage when used with pointers to structs/classes which are unsing inheritance can also be done in the opposite direction, but it is a bit unsafe cause, you are making blind assumptions about what exactly a pointer is pointing to, which can break badly.
when virtual is used:
#include <iostream>
#include <vector>
#include <string>
struct Base {
std::string data;
Base() = default;
virtual void do_something() = 0;
protected:
Base(std::string& a):data{a}{};
};
struct person : public Base {
person(std::string name):
Base(name){}
void do_something() {
std::cout << "im a person and ill do '" << data << "'" << std::endl;
}
};
struct dog : public Base {
dog(std::string name):
Base(name){}
void do_something() {
std::cout << data << "bark bark bark" << std::endl;
}
};
int main(){
std::vector<Base*> d{5};
d[0] = new person{"meu"};
d[1] = new dog{"mutt"};
for (int i{0}; i < 2; i++) {
d[i]->do_something();
}
return 0;
}
then, the layout is:
this, I decided to forcefully show the base classes here
and, the same static_cast rules for the pointer conversions apply as last time
the vptr is a magical pointer to, a magical global array of mysterious function pointers (every compiler uses vptr differently), which at runtime allows the Base* pointer to programatically guess that it is pointing to, a person instance to run person::do_something()
vptr is placed before any fields you have declared in your code.
wow
interesting
so each object store a pointer to the function coresponsing to its child struct?
each dog stores a raw person object in itself,
each person stores a raw Base in itself
for your source code
why dog store person object?
ohhhhhhhhhhhhhhhhhhhhh
wait
I made a mistake
so a dog is a person now 
thanks for explenation
so, now there's one other thing
lets say you have your fancy vector of Base*
buuuuuuuuuut
i would save this for future, cus currently my brain didnt understand 100% of it
you want to call some function that ONLY A PERSON has
and Base doesnt
you are kinda, screwed
well, unless
you do
i'll only do that on private functions... unless it also aply there
for(Base* elem : d)
{
person* p = dynamic_cast<person*>(elem);
if(p != nullptr)
{
p->drive_a_boat();
}
}
then you need this
dynamic_cast will read from vptr secretly and decrypt what exactly is elem pointing to
here, d[1] is not a person
but d[0] is
so, if you accidentally, forcefully overwrite the person object with memcpy, and thus wipe vptr, guess what happens 
hmmm wait so if we cast the object into person* and if the object is not a person struct it would ne nullptr?
yes, a dog cannot be cast into a person
if virtual functions are used, dynamic_cast can be used to safely check
if no virtual functions exist, dynamic_cast doesn't compile
hmmm how the dynamic cast works on run time 🤔
vptr magic
dono that word, doesnt exist. next
"it just works" 😭
checking vptr at runtime can lead to a performance penalty, especially for LARGE inheritance chains
so be careful, if you care about SPEED
when do i even check the vptr 😭
i dont even know what on earth that is
hidden awayyyyyyyy
well, void pointer can point to ints, doubles, arrays, classes, arrays of pointers, anything really
they are EVIL
unless you know how to use them
so, the program will use vptr correctly at runtime
yes, void* types are in use since the C language
yes, and due to NO TYPE SAFETY YOLOOOOOOOOOOOOOOOO
they should be avoided
lmao
they have special uses in low level operating system functions
i wonder how my linter would react if i use void pointer to point to my object...
https://www.youtube.com/results?search_query=cppcon+virtual+table
the real vptr implementation should be shown for some specific compiler in here somewhere
if you are insane enough to want to understand the vptr in depth
hey!!
anything else you need?
@wanton junco Has your question been resolved? If so, type !solved :)
@wanton junco didn't expect to stumble upon you here hitoya! Have a nice day
@wanton junco https://www.youtube.com/watch?v=gWinNE5rd6Q&t=1310s
https://cppcon.org
C++ Under the Hood: Internal Class Mechanisms - Chris Ryan - CppCon 2024
My talk will examine the internal C++ mechanisms around the topics of:
- The C++ onion as it relates to construction, destruction and polymorphism,
- Order of Object construction & destruction, and pre- & post-main() processing.
- Member Func...
watch this
..