#how on earth is this code working

125 messages · Page 1 of 1 (latest)

wanton junco
#

im trying to do something untill i saw some code of the library i use and i say something like this so i make it work but not sure why it happened and how its possible

#bot-spam message

teal lintelBOT
#

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.

wanton junco
#

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

vernal bobcat
#

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;
}
hybrid scaffold
#

what part in particular is surprising to you here?

#

is it about how virtual functions work or how it interacts with the vector?

wanton junco
hybrid scaffold
#

there is no passing of a child going on

wanton junco
#

line 33-34

hybrid scaffold
#

we don't have line numbers

wanton junco
#
  d[0] = new person{"meu"};
  d[1] = new dog{"mutt"};```
#

this part

hybrid scaffold
#

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

wanton junco
#

if its Base* how does it able to acces functionality thats not from Base ?

hybrid scaffold
#

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

wanton junco
#

i wanna learn it more in depth like on the assembly side...

#

i mean compiled side

hybrid scaffold
#

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.

oak tartan
hybrid scaffold
#

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

oak tartan
#

and observe how the pointer casts are actually working

#

let me draw you a diagram

vernal bobcat
oak tartan
#

@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:

#

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.

wanton junco
#

wow

#

interesting

#

so each object store a pointer to the function coresponsing to its child struct?

oak tartan
#

for your source code

wanton junco
#

why dog store person object?

oak tartan
#

wait

#

I made a mistake

wanton junco
#

so a dog is a person now thinkies

oak tartan
#

here, updated

wanton junco
#

thanks for explenation

oak tartan
#

lets say you have your fancy vector of Base*

#

buuuuuuuuuut

wanton junco
#

i would save this for future, cus currently my brain didnt understand 100% of it

oak tartan
#

you want to call some function that ONLY A PERSON has

#

and Base doesnt

#

you are kinda, screwed

#

well, unless

#

you do

wanton junco
oak tartan
#
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 trolol

wanton junco
#

hmmm wait so if we cast the object into person* and if the object is not a person struct it would ne nullptr?

oak tartan
wanton junco
#

fancy word. dono

#

how the code knows you cant cast it?

oak tartan
#

if no virtual functions exist, dynamic_cast doesn't compile

wanton junco
#

hmmm how the dynamic cast works on run time 🤔

oak tartan
wanton junco
#

dono that word, doesnt exist. next

oak tartan
wanton junco
#

"it just works" 😭

oak tartan
#

so be careful, if you care about SPEED

wanton junco
#

when do i even check the vptr 😭

oak tartan
#

it's a void* field

wanton junco
#

i dont even know what on earth that is

oak tartan
#

hidden awayyyyyyyy

wanton junco
#

oh...

#

void pointer okay...

#

wait...

oak tartan
#

yes

wanton junco
#

if its void wtf it is pointing into??

#

my brain....

oak tartan
#

they are EVIL

#

unless you know how to use them

#

so, the program will use vptr correctly at runtime

wanton junco
#

wait so void pointer is a dynamic pointer?

#

i mean..

#

it can point into any type?

oak tartan
oak tartan
wanton junco
#

lmao

oak tartan
#

they have special uses in low level operating system functions

wanton junco
#

i wonder how my linter would react if i use void pointer to point to my object...

oak tartan
#

if you are insane enough to want to understand the vptr in depth

wanton junco
#

hey!!

wanton junco
#

im insane tho....

#

no, thanks for all the info, my brain is currently throttling

teal lintelBOT
#

@wanton junco Has your question been resolved? If so, type !solved :)

neat elbow
#

@wanton junco didn't expect to stumble upon you here hitoya! Have a nice day

lucid island
#

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...
▶ Play video
#

watch this

wanton junco
#

..