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 2

iDog's Interview C++ Part 2

Function, Constructor, Deep copy, Friend, Access Priviledges

Things happen during a function call:


class Cat
{
    // ...
};

Cat cat;

Cat funcByValue(Cat cat) 
{   // cat copied to stack: copy constructor called
    return cat;  // cat copied to stack as the return value;
                 // copy constructor called; [*]
                 // cat1 instantiated
}  // return value destructed first; then argument destructed.

Cat cat1 = funcByValue(cat);

Cat cat2 = cat1;   // copy constructor called, 
                   //   since cat2 not instantiated yet...
cat2 = cat1;       // assignment operator called, 
                   //   since cat2 already instantiated before assignment
Cat cat3(cat1);   // copy constructor called

[*] In g++, the return value won't be copied and destructed, instead, it is copied to cat1 (copy constructor called) directly. If you call funcByValue without assigning the return value to some variable, the return value will be copied and destructed.

[NOTE] This turns to be very complicated nowadays: the behavior differs when you use different compilers. Here are examples showing this.

When you instantiate an object of a derived class, the constructor of the base class is called first, then the constructor of the derived class; when it's destructed, the destructor of the derived class is called first, then that of the base class.

Virtual destructor: if we delete a pointer of base class pointing to an object of derived class, the destructor of the base class will be called... But if we define it as virtual destructor, the correct version of destructor (that of the derived class) will be called, and it will then call destructor of base class, so it can be destructed properly.

Constructor

Constructors are invoked in two stages:

  • initialization stage: initialize member variables listed after constructor's parameter list
  • body: the body of constructor

It's cleaner and often more efficient [*] to initialize member variables at initialization stage:


MyClass::MyClass() : intValue(0), doubleValue(1.234)
{
}
There are some variables that must be initialized and cannot be assigned to: references and constants.

[*] Usually this is true when the member variable is an object. For example:


MyClass::MyClass() : m_obj(myObj) {}
or
MyClass1::MyClass1() {m_obj = myObj;}

In MyClass, m_obj will be init'ed directly using myObj (by calling copy constructor); while in MyClass1, m_obj will be init'ed first by calling its default constructor, then call assignment operator to assign myObj to it.

Initialization of member variables

myclass.h:


class myClass {
private:
    int nn;
    int& rn;

public:
    const char* STR1;
    const static char* STR2;
    const static int N = 0;  // only 'const static int' can be init'ed in class declaration
    const int N1;

public:
    myClass();
    int getInt() {return rn;}
};

myclass.cpp:


#include <iostream>
#include "myclass.h"

using namespace std;

int n = 3;

const char* myClass::STR2 = "this is STR2";

myClass::myClass(): STR1("this is STR1"), N1(1), rn(nn) // rn(n) is also OK
{
    nn = 4;
}

int main()
{
    myClass mc;
    cout << mc.STR1 << endl;
    cout << myClass::STR2 << endl;
    cout << mc.getInt() << endl;
    cout << mc.N1 << endl;

    return 0;
}

Conversion constructors

A conversion constructor is a constructor that can be called with a single argument, so conversions from the type of the argument to the class type can be done. Keyword 'explicit' is used on these constructors to disable the implicit conversion.


class I
{
private:
    int m_i;
public:
    explicit I(int i) : m_i(i) {}
    int value() {return m_i;}
};

void f(const I& i)
{
    ;
}

int main()
{
    I i1(1);                     // OK: normal use of constructor
    I i2 = 2;                    // without 'explicit': OK; explicit: error
    I i3 = (I)3;                 // OK: C style of cast
    I i4 = I(4);                 // OK: old C++ style of cast. like int(3.14).
    I i5 = static_cast<I>(5);    // OK: new style of cast
    
    f(0);                        // without 'explicit': OK; explicit: error    
    
    return 0;
}

Reasons of using 'explicit'

  • make conversions explicit
  • avoid unexpected cost of creating temp instances by implicit conversion

NOTE: implicit conversion cannot transfer: Supposing that following is OK:


class I {
public:
    I(int i) {}
};

class MyInt {
public:
    MyInt(const I& i) {}
};

I i = 0;       // OK
MyInt mi = i;  // OK
MyInt mi1 = 1; // error

NOTE: 'explicit' only works on constructors that can be called with one argument.


class C {
public:
    explicit C(int i) {}
    explicit C(double d1, double d2 = 0) {}
    explicit C(float f1, float f2) {}  // here the 'explicit' keyword is of no use
};

Copy constructor

Copy constructor takes a const reference of the class as its parameter.


MyClass::MyClass(const MyClass& myClass) {...}

The default copy constructor simply copies each member variable from the object passed as a parameter to the member variables of the new object. This is called a member-wise (or shallow) copy. This is fine for most member variables, but it doesn't work in the expected way for member variables that are pointers to objects on the free store.

A shallow or member-wise copy copies the exact values of one object's member variables into another object. Pointers in both objects end up pointing to the same memory. A deep copy copies the values allocated on the heap to newly allocated memory.

Warning: if there are dynamically allocated memory blocks in a class, you should write your own copy constructor to do deep copies to these objects.

Deep copy

If there are dynamically allocated objects in the class, you should delete these objects in your assignment operator before doing the deep copy (otherwise the memory won't be freed). But here is another problem: if you assign the object to itself, the objects will be deleted before using them... So the assignment operator should test if the right hand side of the assignment operator is the object itself.


MyClass& MyClass::operator=(const MyClass& myClass) {
    if(this == &myClass)  return *this;

    // do the assignment work
    ...

    return *this;
}

There are basically 3 common ways to test if two objects are identical:


if(this == &myClass)  return *this;
but if you are using multiple inheritance, the above test will fail. (think about polymorphism)


if(*this == myClass)  return *this;
Here == operator has to be defined (to compare with const variable). And of course you can define what it means for the objects to be equal.

And the third way: let the class to contain an ID, so you can just compare the IDs of the objects:


if(this.m_ID == myClass.m_ID)  return *this;

Assignment operator vs. copy constructor

The difference between an assignment operator and a copy constructor is if the lhs already exists or not. When you assign an object to another, the object being assigned to is already created and initialized; on the contrary, when you use copy constructor, the object has not yet been created.

Friend

Format of declaring friend classes:


class MyClass
{
    friend class FriendOfMyClass;
    // ...
};

Format of friend functions:


class MyClass
{
    friend void friendFunc1OfMyClass();
    friend void AnotherClass::friendFunc2OfMyClas();
    // ...
};

Access Privileges

  • public members: free access
  • protected members: can only be accessed by member functions and derived classes
  • private members: can only be accessed by member functions
  • friends have full access to all members

Access privileges of members of base class in derived class


class Base
{
protected:  // <-- access in base class
    int m_nData;
    // ...
};

class Derived : public Base // <-- Base class inherited as
{
    // ...
};

Base class inherited as
publicprotectedprivate
Access in base classpublicpublicprotectedprivate
protectedprotectedprotectedprivate
privateno accessno accessno access

  • derived class has no access to private members of base class
  • public inheritance: access priviledges to public/protected members of base class in derived class doesn't change
  • protected inheritance: access priviledges to public/protected members of base class in derived class changed to protected.
  • private inheritance: access priviledges to public/protected members of base class in derived class changed to private.
  • we can also memorize the rules in this way: other than private members in base class, the eventual access privilege is the minimum of 'access in base class' & 'base class inherited as'.