Print service provided by iDogiCat: http://www.idogicat.com/
home logo





Home > IT > Programming > C++ Programming > iDog's Interview C++ > iDog's Interview C++ Part 4

iDog's Interview C++ Part 4

Inheritance, Virtual function

Inheritance and derivation


class Base
{
protected:
    int baseMember;
    int initedBaseMember;
public:
    Base(int val) : initedBaseMember(val) {}
};

class Derived : public Base
{
private:
    int derivedMember;
public:
    Derived(int baseVal, int initedBaseVal, int DerivedVal);
};

Derived::Derived(int baseVal, int initedBaseVal, int derivedVal)
        : Base(initedBaseVal)
        , derivedMember(derivedVal)
{
    baseMember = baseVal;      // [*]
}

[*] the 'baseMember' must be init'ed in the body of the constructor. It should not be init'ed in the initialization stage, because at that time, the Base has not been constructed yet.

Virturl function

When a virtual function is created in an object, the object must keep track of that function. Many compilers build a virtual function table, called a v-table. One of these is kept for each type, and each object of that type keeps a virtual table pointer (called a vptr or v-pointer), which points to that table.

Each object's vptr points to the v-table which, in turn, has a pointer to each of the virtual functions. When the Mammal part of the Dog is created, the vptr is initialized to point to the correct part of the v-table; When the Dog constructor is called, and the Dog part of this object is added, the vptr is adjusted to point to the virtual function overrides (if any) in the Dog object.

Virtual Table:


class Base {
public:
    virtual void func1();
    virtual void func2();
    virtual void func3();
};

class Derived : public Base {
};

Base baseObj;
Derived derivedObj1;
Derived derivedObj2;

v-table

About virtual functions:

  • The virtual function magic only works for pointers and references to the base class. Objects doesn't work this way.
  • A pointer to base class doesn't have access to functions in derived class but not in base class
  • If the destructor is declared as virtual function, when you delete a pointer of base class type but pointing to an instance of derived class, the destructor of the derived class will be called, and it will then call the destructor of the base class, so both of them can be destructed properly.
  • Constructors cannot be virtual. In order to construct in the way of polymorphism, a virtual clone function can be defined.


Dog dog();
Mammal m = dog;
m.speak();  // call the version in class Mammal

Mammal& rm = dog;
rm.speak();  // call the version in class Dog

Mammal* pm = &dog;
pm->speak();  // call the version in class Dog


// virtual clone function
class Base
{
    virtual Base* clone() {return new Base(*this);}
};

class Derived
{
    virtual Base* clone() {return new Derived(*this);}
};

// usage
Base* pb = new Derived();
Base* pCloned = pb->clone();

dynamic binding


class Base {
    virtual void func() const { cout << "func in Base" << end;}
};

class Derived1 : public Base {
    void func() const { cout << "func in Derived1" << end;}
};

class Derived2 : public Base {
    void func() const { cout << "func in Derived2" << end;}
};

// use above classes in code...

Base* pBase;

if(bUseDerived1) {
    pBase = new Derived1;
} else {
    pBase = new Derived2;
}

pBase->func();

At compile time, it is impossible to know which objects will be created, and thus which version of the func() methods will be invoked. The pointer pBase is bound to its object at runtime. This is called dynamic binding, or run-time binding, as opposed to static binding, or compile-time binding.

Calling functions in base class:


pm->Mammal::speak();
rm.Mammal::speak();

When to use virtual functions

  • No constructor can be virtual
  • If there are dynamically allocated objects, the destructor should be defined to be virtual.
  • As a rule of thumb, if there is a virtual function in a class, the destructor should also be defined as virtual (although it might not be necessary sometimes).

Cost of Virtual Methods

Because objects with virtual methods must maintain a v-table, there is some overhead in having virtual methods. If you have a very small class from which you do not expect to derive other classes, there may be no reason to have any virtual methods at all.

Once you declare any methods virtual, you've paid most of the price of the v-table (although each entry does add a small memory overhead). At that point, you'll want the destructor to be virtual, and the assumption will be that all other methods probably will be virtual as well. Take a long hard look at any non-virtual methods, and be certain you understand why they are not virtual.

Q: Why not make all class functions virtual?

A: There is overhead with the first virtual function in the creation of a v-table. After that, the overhead is trivial. Many C++ programmers feel that if one function is virtual, all others should be. Other programmers disagree, feeling that there should always be a reason for what you do.

Note that all functions in a Java class are virtual functions.

Pure virtual function


class MyClass
{
    virtual <i>return_type</i> <i>func_name</i>(<i>parameter_list</i>) = 0;
};

Classes containing pure virtual functions are called abstract classes. They cannot be instanciated. If its derived class doesn't implement the pure virtual function, then the derived class is also an abstract class.

In ADT, the pure virtual functions can also be implemented. The only purpose of these implementations are to be used by their overriden functions in the derived class.


Class Base // abstract class
{
public:
    virtual int vf() = 0;
};

int Base::vf()
{
    // some implementation
    return 0;
}

class Derived : public Base
{
    int vf();
};

int Derived::vf()
{
    // ...
    Base::vf();  // use the version defined in Base
    // ...
    return xxx;
}

Hiding

In the base class, there are two overloaded functions; in the derived class, only one of the functions is overriden. No matter if these functions are virtual or not, for a pointer/reference to or an object of the derived class, the function in the base class which is not overriden is not accessible.


class Base
{
public:
    virtual void func() {}
    virtual void func(int) {}
};

class Derived : public Base
{
public:
    virtual void func() {}  // "virtual" is omitable here
};

Derived d;
d.func(1);  // compiler error
Derived* pd = new Derived();
pd->func(1);  // compiler error

d.Base::func(1); // OK
pd->Base::func(1); // OK

Casting down

Cast a "pointer to a base class" to a "pointer to the derived class". The dynamic_cast operator should be used. If the pointer cannot be cast down, the operator will return null.

The need of casting down is a warning that something may be wrong with the design.


Derived* pDerived = dynamic_cast&lt;Derived*>(pBase);
if(pDerived != null) {
    // successfully casted it down
} else {
    // cannot cast it down! It's really pointing to an object of Base
}

RTTI

RTTI refers to "Runtime Type Identification". IMHO, it's not so useful, and it affects the overall performance, so many compilers disable it by default.

'typeid' operator is used on objects, references and class names, it returns an object of type_info type. You need to include <typeinfo> header file.

Note that typeid operator only returns the class type, it doesn't care about things like const and volatile.


#include <typeinfo>
#include <assert.h>

class A {}; 
class B {};

class DA : public A {};

int main()
{
    A a;
    const A ca;
    volatile A va;

    const A* const cpa = &ca;

    DA da;
    A* pa = &da;

    B b;
    B& rb = b;

    // all following assertions are true
    assert(typeid(a)  == typeid(A));
    assert(typeid(ca) == typeid(*cpa));

    assert(typeid(rb) == typeid(B));
    assert(typeid(a)  != typeid(rb));

    assert(typeid(pa) != typeid(A));

    assert(typeid(ca) == typeid(A));
    assert(typeid(va) == typeid(A));

    return 0;
}