Showing posts with label inherit. Show all posts
Showing posts with label inherit. Show all posts

Saturday, March 18, 2017

Some C/C++ tips & tricks

Understanding Virtual Functions in C++

Virtual functions are a cornerstone of C++ polymorphism, enabling dynamic binding at runtime. This blog post delves into various aspects of virtual functions with practical code examples and detailed explanations.

Virtual Function Example

Let's start with a basic example that demonstrates the use of virtual functions.

[code]

#include<iostream>
using namespace std;

class Base{
public:
    int a;
        virtual void sum() {
        cout << "sum of class Base" << endl;
    }
};

class Derived : public Base {
public:
    int b;
    void sum() {
         cout << "sum of class Derived" << endl;
    }

};

int main() {
    Base *aptr;
    Derived d;
    aptr = &d;
    aptr->sum();
    return 0;
}


Output:


sum of class Derived

Without the virtual keyword, the output would be "Sum of class Base."


sum of class Base


Virtual Friend Function Idiom

This idiom allows friend functions to act as if they were dynamically bound:


[code]

#include<iostream>
using namespace std;

class Base {
    public:
       friend ostream& operator << (ostream& o, const Base& b);
    protected:
       virtual void print(ostream& o) const
       {  cout << "This is Base class print function" << endl;    }
};

/* make sure to put this function into the header file */
inline std::ostream& operator<< (std::ostream& o, const Base& b)
{
      b.print(o); // delegate the work to a polymorphic member function.
      return o;
}

class Derived : public Base {
  protected:
    virtual void print(ostream& o) const
    { cout << "This is Derived class print function" << endl; }
};

int main(void)
{
     Base b;
     cout << b;
     Derived d;
     cout << d;
     return 0;
}
Output:

This is Base class print function
This is Derived class print function

The end result is that operator<< acts as if it were dynamically bound, even though it's a friend function.
This is called the Virtual Friend Function Idiom. Note that derived classes override printOn(std::ostream&) const. In particular, they do not provide their own operator<<.

Confusing Base/Derived class pointer conversion

This example demonstrates dangerous conversions between base and derived class pointers:
#include<iostream>
using namespace std;

class BB{
public:
         virtual void Doit() { cout << "BB do it" << endl; }
         void Seeit() { cout << "BB see it" << endl; }
         virtual ~BB(){}
};

class DD: public BB{
public:
         virtual void Doit() { cout << "DD do it" << endl; }
         void Seeit() { cout << "DD see it" << endl; }
};

class XX: public BB{
public:
         void Seeit() { cout << "XX see it" << endl; }
         virtual void Doit() { cout << "XX do it" << endl; }
};

int main(void)
{
    DD *dp = new DD;

    BB *bp = static_cast<BB*>(dp);
    bp->Doit();
    bp->Seeit();

    XX *xp = static_cast<XX *>(bp);   //dangerous, please don't do it
    xp->Doit();
    xp->Seeit();

    return 0;
}


Output:
DD do it
BB see it
DD do it
XX see it


However, if we change the main function as follows (by using 'dynamic_cast' instead):

int main(void)
{
    DD *dp = new DD;

    BB *bp = dynamic_cast<BB*>(dp);
    bp->Doit();
    bp->Seeit();

    XX *xp = dynamic_cast<XX *>(bp);
    if (xp == NULL) {
        cout << "Wrong, this should not be allowed" << endl;
    } else {
        xp->Doit();
        xp->Seeit();
    }
    return 0;
}



Constructor and Destructor Calls in Inheritance

When a derived class instance is created, the base class constructor is called first, followed by the derived class constructor. On destruction, the process is reversed.

For example:

[code]   

#include<iostream>
using namespace std;
class Other{
    public:
       int ov;
       Other(int t):ov(t) { cout << "Other constructor" << endl; }
       ~Other(void) { cout << "Other destructor " << endl; }
};
class Base{
public:
    int bv;
    Base(int var):bv(var)
    {
        cout<<"Base constructor"<<endl;
    }
    virtual ~Base(void)
    {
        cout<<"Base destructor"<<endl;
    }
};
class Derived: public Base
{
public:
    int dv;
    Other mt;
    Derived(int d): mt(d++), Base (d++)     // Base class constructor gets call first, then followed by member initialization
    {
        cout<<"child constructor"<<endl;
    }
    ~Derived(void) {
        cout << "child destructor" << endl;
    }
};
int main()
{
    Derived obj1(8);
}

 

The expect result is:

Base constructor
Other constructor
child constructor
child destructor
Other destructor 
Base destructor


Private Virtual Function Usage

Even private virtual functions can be used to enforce derived class behavior without being directly called by the derived class.


[code]

#include<iostream>
using namespace std;

class A{
    public:
       void foo() {
          cout << __PRETTY_FUNCTION__ << endl;
          bar();
       }
    private:
       virtual void bar() {
          cout << __PRETTY_FUNCTION__ << endl;
       }
};

class B: public A {
   private:
       virtual void bar() {
          cout << __PRETTY_FUNCTION__ << endl;
       }
};

int main(void)
{
    A *pa = new B;
    pa->foo();
    return 0;
}
 

Base class requires its Derived class to override its virtual function bar(),  but  it is better for its derived class not call it directly. so the expected result is:

void A::foo()
virtual void B::bar()


Polymorphism in Constructors and Destructors

Virtual functions cannot exhibit polymorphism in constructors and destructors.


#include<iostream>
using namespace std;

class A{
    public:
       A() {
          cout << __PRETTY_FUNCTION__ << endl;
          bar();
       }
       ~A() {
          cout << __PRETTY_FUNCTION__ << endl;
          bar();
       }
       virtual void bar() {
          cout << __PRETTY_FUNCTION__ << endl;
       }
};

class B: public A {
   public:
       virtual void bar() {
          cout << __PRETTY_FUNCTION__ << endl;
       }
};

int main(void)
{
    A *pa = new B;
    delete pa;
    return 0;
}

I would expect the result should be:
<=====Wrong result =====>
A::A()
virtual void B::bar()
A::~A()
virtual void B::bar()
<=====End Wrong result =====>

but the actual result is:

A::A()
virtual void A::bar()
A::~A()
virtual void A::bar()

Pure virtual function

Polymorphism won't be available if a member function is not virtual in the base class. Let's consider the following example:

[code]

#include<iostream>
using namespace std;

class Abase
{
public:
    virtual void FA()= 0;
    void FB()   // notice that there is no virtual here
    {
        cout << __PRETTY_FUNCTION__ << endl;
    }
    virtual void FC()
    {
       cout << __PRETTY_FUNCTION__ << endl;
    }
};

//sub class
class Subase: public Abase
{
public:
    void FA()
    {
       cout << __PRETTY_FUNCTION__ << endl;
    }
    void FB()
    {
       cout << __PRETTY_FUNCTION__ << endl;
    }
    void FC()
    {
      cout << __PRETTY_FUNCTION__ << endl;
    }
};

int main()
{
    Abase* inst = new Subase();
    inst->FA();
    inst->FB();
    inst->FC();
    return 0;

}



The result is:
virtual void Subase::FA()
void Abase::FB()
virtual void Subase::FC()

How many instances get created and which constructor is invoked

Please do some experiment in the following class: 

[code]
#include<iostream>
#include<string>

using namespace std;
#define TRACEPR cout << __PRETTY_FUNCTION__ << endl;

class B{
    private:
       int m = 0;
    public:
       B(int b=0):m(b){
           TRACEPR
       };
       B(const B &b):m(b.m) {
           TRACEPR
       };
       B &operator=(const B &b) {
          TRACEPR
          m = b.m;
          return *this;
       }
       virtual ~B(void) {
           TRACEPR
       }
       B(const B &&b):m(b.m) {
           TRACEPR
       }
       B &operator=(B &&b) {
          TRACEPR
          m = b.m;
          b.m = 0;
          return *this;
       }
};

B getB()
{
     B b;
     return b;
}

B &getrB()
{
     static B b;
     return b;
}

void setB(B b)
{
     B t = b;
}

void setrB( B &b)
{
     B t = b;
}


The compiler specific options as follows:

g++  -g -std=c++11 -fno-elide-constructors  runfile.cpp  -o runfile



Please inspect the following invoke combinations (some have compiler error) , pay attention about what constructor is called and how many instances are created, including xvalue, rvalue and lvalue type. 

1) B b = getB();
2) const B &b = getB(); // what if we don't use const?
3) B b = getrB(); 
4) B &b = getB();
5) setB(b);
6) setrB(b);




Object Slicing  

Object slicing occurs when a derived class object is assigned to a base class object, causing the derived part to be "sliced off."



#include<iostream>
using namespace std;


class B{
    public:
       virtual void F() { cout << "B F() "  << endl; }
       void C() { cout << "B C() " << endl; }
};


class D: public B{
    public:
       void F() { cout << "D F() "  << endl; }
       void C() { cout << "D C() " << endl; }
};


void objnotslicing(B &b)
{
     b.F();
     b.C();
}

void objslicing( B b )
{
     b.F();
     b.C();
}

int main(void)
{
     D d;
     objnotslicing(d);
     objslicing(d);
     return 0;
}



The result is:

// object not slicing happens
D F()
B C()     // please note that C() is a virtual function, hence the polymorphism.

// object slicing happens
B F()
B C()


Understanding and utilizing virtual functions correctly can significantly enhance the flexibility and functionality of your C++ programs. From dynamic binding to preventing object slicing, virtual functions are indispensable tools in advanced C++ programming. Experiment with these examples to deepen your understanding and see the power of virtual functions in action.








Wednesday, February 1, 2017

C++ Virtual Table Example


C++ Vtable Example
Revised 10 September 1999


[990910 IBM -- Brian] Added more examples, split out the two kinds of adjustments in Table 1a, and added a summary of the component counts for the two approaches.


Table 1a: Example Code and Call Semantics 
Declarations Call Callee Call-site
Adjustment 
Thunk/Entry-point
Adjustment
struct A {
  virtual void f ();
  virtual void g ();
  virtual void h ();
  int ia;
};

A *pa;
pa->f()A::f() none none
pa->g()A::g() none none
pa->h()A::h()nonenone
struct B: public virtual A {
  void f ();
  void h ();
  int ib;
};

B *pb;
A *pa_in_b = pb;
pb->f()B::f() none none
pb->A::f()A::f() B => Anone
pb->g()A::g() B => A none
pb->h()B::h()nonenone
pa_in_b->f()B::f() none A => B
pa_in_b->g()A::g() none none
pa_in_b->h()B::h() none A => B
pa_in_b->A::f()A::f() none none
struct C: public virtual A {
  void g ();
  void h ();
  int ic;
};

C *pc;
A *pa_in_c = pc;
pc->f()A::f() C => A none
pc->g()C::g() none none
pc->A::g()A::g() C => A none
pc->h()C::h() none none
pa_in_c->f()A::f() none none
pa_in_c->g()C::g() noneA => C
pa_in_c->h()C::h() noneA => C
pa_in_c->A::g()A::g() none none
struct D: public B, public C {
  int id;
  void h();
};

D *pd;


A *pa_in_d = pd;
B *pb_in_d = pd;
C *pc_in_d = pd;


A *pa_in_b_in_d = pb_in_d;
A *pa_in_c_in_d = pc_in_d;
pd->f()B::f() none [D => B] none
pd->g()C::g() D => C none
pd->h()D::h() none none
pa_in_d->f()B::f() none A => B
pa_in_d->g()C::g() noneA => C
pa_in_d->h()D::h() noneA => D
pb_in_d->f()B::f() none none
pb_in_d->g()C::g() B => AA => C
pb_in_d->h()D::h() none B => D
pc_in_d->f()B::f() C => AA => B
pc_in_d->g()C::g() none none
pc_in_d->h()D::h() none C => D
pa_in_b_in_d->f()same as for pa_in_d 
pa_in_b_in_d->g()
pa_in_b_in_d->h()
pa_in_c_in_d->f()
pa_in_c_in_d->g()
pa_in_c_in_d->h()
p...d->A::f()A::f() ... => A none
p...d->A::g()A::g() ... => A none
p...d->A::h()A::g() ... => A none
struct X {
  int ix;
  virtual void x();
};
struct E : X, D {
  int ie;
  void f();
  void h();
};
pe->f()E::f()nonenone
pe->g()C::g()E => Cnone
pe->h()E::h()none none
pe->x()X::x()none [E=>X]none
pa_in_e->f()
E::f()noneA => E
pa_in_e->g()C::g()noneA => C
pa_in_e->h()E::h()noneA => E
pb_in_e->f()E::f()noneB => E
pb_in_e->g()C::g()B => AA => C
pb_in_e->h()E::h()none B => E
pc_in_e->f()E::f()C => AA => E
pc_in_e->g()C::g()nonenone
pc_in_e->h()E::h()noneC => E
pd_in_e->f()E::f()none [D=>B]B => E
pd_in_e->g()C::g()D => Cnone
pd_in_e->h()E::h()noneD => E

Table 1b: Example Data Layout 
Declarations Size OffsetMember 
struct A {
  virtual void f ();
  virtual void g ();
  virtual void h ();
  int ia;
};
16 0A::vptr
ia
struct B: public virtual A {
  void f ();
  void h ();
  int ib;
};
32 0B::vptr
ib
16 A::vptr
24 ia
struct C: public virtual A {
  void g ();
  void h ();
  int ic;
};
32 0C::vptr
ic
16 A::vptr
24 ia
struct D: public B, public C {
  void h ();
  int id;
};
48 0D/B::vptr
ib
16 C::vptr
24 ic
28 id
32 A::vptr
40 ia
struct X {
  int ix;
  virtual void x();
};
struct E : X, D {
  void f ();
  void h ();
  int ie;
};
640X/E::vptr
8ix
16D/B::vptr
24ib
32C::vptr
40ic
48id
56A::vptr
64ia

Table 1c: Example Vtable Layout 
Declarations Vtable (HP) 1,2,3Vtable (Cygnus/IBM)
struct A {
  virtual void f ();
  virtual void g ();
  virtual void h ();
  int ia;
};
A::offset_to_top (0)
A::rtti
-- A vtable address --
A::f() []
A::g() []
A::h() []
A::offset_to_top (0)
A::rtti
-- A vtable address --
A::f() []
A::g() []
A::h() []
struct B: public virtual A {
  void f ();
  void h ();
  int ib;
};
B::offset_to_A (16)
B::offset_to_top (0)
B::rtti
-- B vtable address --
B::f() []
B::h() []

A::offset_to_top (-16)
A::rtti
-- A-in-B vtable address --
B::f() [[-72] B::offset_to_A : thunk]
A::g() []
B::h() [[-72] B::offset_to_A : thunk]
B::offset_to_A (16)
B::offset_to_top (0)
B::rtti
-- B vtable address --
B::f() []
B::h() []

A::offset_for_h (-16)
A::offset_for_g (0)
A::offset_for_f (-16)
A::offset_to_top (-16)
A::rtti
-- A-in-B vtable address --
B::f() [[-24]offset_for_f]
A::g() []
B::h() [[-40]offset_for_h]
struct C: public virtual A {
  void g ();
  void h ();
  int ic;
};
C::offset_to_A (16)
C::offset_to_top (0)
C::rtti
-- C vtable address --
C::g() []
C::h() []

A::offset_to_top (-16)
A::rtti
-- A-in-C vtable address --
A::f() []
C::g() [[-72] C::offset_to_A : thunk]
C::h() [[-72] C::offset_to_A : thunk]
total size 15*8 = 120 bytes
C::offset_to_A (16)
C::offset_to_top (0)
C::rtti
C vtable address --
C::g() []
C::h() []

A::offset_for_h (-16)
A::offset_for_g (-16)
A::offset_for_f (0)
A::offset_to_top (-16)
A::rtti
A-in-C vtable address --
A::f() []
C::g() [[-32] offset_for_g]
C::h() [[-40] offset_for_h]
total size 18*8 = 144 bytes
struct D: public B, public C {
  void h ();
  int id;
};
D::offset_to_C (16)
D::offset_to_A (32)
D::offset_to_top (0)
D::rtti
-- D, B-in-D vtable address --
B::f() []
D::h() []

C::offset_to_A (16)
C::offset_to_top (-16)
C::rtti
-- C-in-D vtable address --
C::g() []
D::h() [[-88] D::offset_to_C]

A::offset_to_top (-32)
A::rtti
-- A-in-D vtable address --
B::f() [[-128] D::offset_to_A : thunk]
C::g() [[-72] C::offset_to_A : thunk]
D::h() [[-128] D::offset_to_A : thunk]

total size 23*8 = 184 bytes
D::offset_to_A (32)
D::offset_to_top (0)
D::rtti
-- D, B-in-D vtable address --
B::f() []
D::h() []

C::offset_to_A (16)
C::offset_to_top (-16)
C::rtti
-- C-in-D vtable address --
C::g() []
D::h() [-16]

A::offset_for_h (-32)
A::offset_for_g (-16)
A::offset_for_f (-32)
A::offset_to_top (-32)
A::rtti
-- A-in-D vtable address --
B::f() [[-24] offset_for_f]
C::g() [[-32] offset_for_g]
D::h() [[-40] offset_for_h]
total size 25*8 = 200 bytes
struct X {
  int ix;
  virtual void x();
};
struct E : X, D {
  int ie;
  void f();
  void h ();
};
E::offset_to_D (16)
not used 
not used
not used
not used
E::offset_to_C (32)
E::offset_to_A (56)
E::offset_to_top (0)
E::rtti
-- E, X-in-E vtable address --
X::x() []
E::f() []
E::h() []

D::offset_to_A (40)
D::offset_to_top (-16)
D::rtti
-- D, B-in-E vtable address --
E::f() [[-144] E::offset_to_D]
E::h() [[-144] E::offset_to_D]

C::offset_to_A (24)
C::offset_to_top (-32)
C::rtti
-- C-in-E vtable address --
C::g() []
E::h() [[-144] E::offset_to_C]

A::offset_to_top (-56)
A::rtti
-- A-in-E vtable address --
E::f() [[-200] E::offset_to_A : thunk]
C::g() [[-72] C::offset_to_A : thunk]
E::h() [[-200] E::offset_to_A : thunk]
total size 37*8 = 296 bytes
E::offset_to_A (56)
E::offset_to_top (0)
E::rtti
-- E, X-in-E vtable address --
X::x() []
E::f() []
E::h() []

D::offset_to_A (40)
D::offset_to_top (-16)
D::rtti
-- D, B-in-E vtable address --
E::f() [-16]
E::h() [-16]

C::offset_to_A (24)
C::offset_to_top (-32)
C::rtti
-- C-in-E vtable address --
C::g() []
E::h() [-32]

A::offset_for_h (-56)
A::offset_for_g (-24)
A::offset_for_f (-56)
A::offset_to_top (-56)
A::rtti
-- A-in-E vtable address --
E::f() [[-24] A::offset_for_f ]
C::g() [[-32] A::offset_for_g ]
E::h() [[-40] A::offset_for_h ]
total size 34*8 = 272 bytes
  1. Numbers in parentheses after offset_to_top entries are actual values.
  2. Class prefixes for functions identify class where defined.
  3. Information in square brackets after function pointer entries indicates entry-point adjustment:
    [] no adjustment required, use primary entry point
    [n] use adjusting entry point that adds "n" to this[[n] blurb]  use adjusting entry point that dereferences vptr+n and subtracts (HP) or adds (Cygnus/IBM)
        that value to this. blurb is the name of the accessed  field
    [[n] blub : thunk]  use adjusting 3rd party thunk that dereferences vptr+n and subtracts that value from this
Notes: 1) Each function descriptor in the vtable is 16 bytes but the offset and data pointers are only 8, the earlier versions of this table didn't take that into account
2) In the HP column for struct E, I have omitted the D::offset_to_C field because the overrides in E render it unnecessary.  However, if maintaining navigability inside the nonvirtual parts of the vtable is important then this "cleanup" can only be done for direct nonvirtual bases and not for more deeply nested ones.
3) I have taken Christophe at his word that thunks are used for adjusting vtable entries in virtual bases in the HP proposal. Some of them could be done with entry points though.
When all is said and done we have

x/y/z
x = # direct secondary entries
y = # "reach back" secondary entries
z = # 3rd-party thunks
FunctionHPCygnus/IBM
A::f0/0/00/0/0
A::g0/0/00/0/0
A::h0/0/00/0/0
B::f0/0/20/1/0
B::h0/0/10/1/0
C::g0/0/10/1/0
C::h0/0/10/1/0
D::h0/1/11/1/0
E::f0/1/11/1/0
E::h0/1/12/1/0