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 5

iDog's Interview C++ Part 5

Overload & override, Multiple inheritance, Abstract data type, Static

Overloaded Functions & Overriden Functions

  • Overloaded function: function defined with same name but different "parameter list + const modifier" of another function
  • Overriden function: function defined in a derived class with the same signature/return type as the one defined in base class.

 return typefunction nameParameter list & const modifier
overloadingsame or differentsamedifferent
overridingsamesamesame

Ambiguity that can happen to overloaded functions:


void func(int n);
void func(int n1, int n2 = 0);

// call function
func(2);  // compiler error

Multiple Inheritance

Problem of ambiguity: if a class derives from two base classes, and each of the two base classes has a function with the same name. When you call the function in the derived class or in the client code using the derived class, you should specify which function you want to use:


class Base1
{
public:
    void func() {}
};
class Base2
{
public:
    void func() {}
};

class Derived : public Base1, public Base2 {
}

int main() {
    Derived d;
    d.Base1::func();  // must specify which one to call
    return 0;
}

If the two base classes sharing a same base class, then in the derived class, there are two copies of the base class, and you need to specify by which way a function from the base class comes when you use it:


class Base {
public:
    void func() {}
};
class Derived1 : public Base {};
class Derived2 : public Base {};
class Derived : public Derived1, public Derived2 {};

int main() {
    Derived d;
    d.Derived1::func();
    return 0;
}

If this is the case, a better solution is to override the func() in Derived class to isolate the details inside the class, thus the client doesn't need to care about the details:


class Derived : public Derived1, public Derived2 {
public:
    void func() {Derived1::func();}
};

int main() {
    Derived d;
    d.func();
    return 0;
}

Another solution, and the best one, is to use virtual inheritance.

Virtual Inheritance

If two classes inheritate virtually from the same base class, then in the derived class of the two classes, only one copy of the base class exists.

Another change is about the initialization of the base classes. Normally a class's constructor only initializes variables of its own and the its base class. However, virtually inherited classes are exceptions, they are initialized by their most derived class, and the initialization processes in their direct inherited classes are ignored.


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

class Derived1 : virtual public Base
{
    int d1;
public:
    Derived1(int bv, int d1v)
        : Base(bv)
        , d1(d1v) {}
};

class Derived2 : virtual public Base
{
    int d2;
public:
    Derived2(int bv, int d2v)
        : Base(bv)
        , d2(d2v) {}
};

class MyClass : public Derived1, public Derived2
{
    int m;
public:
    MyClass(int bv, int dbv, int d1v, int d2v, int mv) 
        : Base(bv)
        , Derived1(dbv, d1v)
        , Derived2(dbv, d2v)
        , m(mv) {}
};

int main()
{
    MyClass my(1, 2, 3, 4, 5);   // initializations in Derived1 and Derived2 for bv are ignored
    my.func();   // no ambiguity here
    return 0;
}

Capability Classes & Abstract Data Types

A capability class, is a class that adds functionality without adding much or any data. This goes between single and multiple inheritance.

An Abstract Data Type represents a concept rather than an object. In C++, an ADT is always the base class to other classes, and it is not valid to make an instance of an ADT. In Java, it's called an 'interface'.

C++ supports the creation of abstract data types with pure virtual functions. A virtual function is made pure by initializing it with zero, as in


virtual void Draw() = 0; 

Any class that derives from an ADT inherits the pure virtual function as pure, and so must override every pure virtual function if it wants to instantiate objects.

Typically, the pure virtual functions in an abstract base class are never implemented.

It is possible, however, to provide an implementation to a pure virtual function. The function can then be called by objects derived from the ADT to provide common functionality to all the overridden functions.

The level of abstraction is a function of how finely you need to distinguish your types. If you are writing a program that depicts a farm or a zoo, you may want Animal to be an abstract data type, but Dog to be a class from which you can instantiate objects. On the other hand, if you are making an animated kennel, you may want to keep Dog as an abstract data type, and only instantiate types of dogs: retrievers, terriers, and so forth.

Percolating functionality upwards means moving shared functionality upwards into a common base class. If more than one class shares a function, it's desirable to find a common base class in which that function can be stored. But it's not a good idea to move upward a function if not all derived classes can use the function, otherwise you would have to use runtime type identification to decide if you can invoke the function in a certain derived class (Suppose you have a pointer to the base class which points to an object of one of the derived classes, but you don't know what exactly it is...).

It's not a good idea to use runtime type identification, because virtual functions are designed to let the virtual table - rather than the programmer - determine the runtime type of the object.

Static

Static member variables:

Only one copy is kept in memory, it is shared among all instances of the class.

A static member variable can be used to track the number of instances of the class created, and if you check it before creating a new instance, you can control the maximum number of instances that can be created.

Static member variables can be accessed by non-static member functions.

Static Data vs Global Data

Static data is scoped to the class, i.e., they are available only to objects of the class, static member functions of the class, and explicit calls using the class name if the static variable is public. This restricted the access thus it's better than global data.

Notes on static member functions:

  • Static member functions can be called without an instance of the class
  • In static member functions, you cannot use the 'this' pointer
  • Static member functions cannot be declared const (since no 'this' pointer: in const funcion, 'this' is a const 'this')
  • Static member functions cannot access any non-static member variables.

Containment, Delegation & Implementing in terms of

  • Containment (composition) - A class contains another class.
  • Delegation - Using the attributes of a contained class to accomplish functions not otherwise available to the containing class.
  • Implemented in terms of - Building one class on the capabilities of another without using public inheritance.

Inheritance, containment and deligation

Public inheritance expresses is-a, containment expresses has-a, and private inheritance expresses implemented in terms of. The relationship delegates to can be expressed using either containment or private inheritance, though containment is more common.

Why is containment preferred over private inheritance?

The challenge in modern programming is to cope with complexity. The more you can use objects as black boxes, the fewer details you have to worry about and the more complexity you can manage. Contained classes hide their details; private inheritance exposes the implementation details.

A rule of thumb: Use composition when you can, private inheritance when you have to (access base's protected members). Usually private inheritance is a temp solution.