C++ Study Notes

Inline Function and Its Address

Of course inline function has no address, but you can pass the "address" of that inline function. In that case inline function is not gonna be expanded, i.e., the inline modifier will be ignored. For example, recursive inline function will not be expanded because recursive function need the address of itself. In general, inline is just a hint/suggestion, not a mandate, just like register modifier, compilers are free to ignore it. And, of course, inline functions are not expanded in debug builds,

Inline Virtual Function

A virtual function has to have an address since we need it in virtual function table or V-table to make polymorphism work. So for compilers, the easiest way to deal with inline virtual function is to ignore the inline modifier.

If the compiler is willing to do some more analysis, then it is possible to make the function both inline and virtual. For example, in the code shown below, it is easy to infer that pBase->foo() is Base::foo() during compile time.

class Base
{
    inline virtual foo();
};

class Deriv : public Base
{
    inline virtual foo();
}

Base x; 
Base* pBase = &x; 
pBase->foo(); 

Public, Private, Protected

An instance of a class can only access the public data and member functions.

For a derived class of a base class and its instance, depending on the type of inheritance, there are three cases:

  • Public Inheritance: The derived class has no access to the private data and private member functions of the base class. The public and protected member of the base class are inherited as the public and protected member of the derived class.
    • [public inheritance] + [public member] = [public member]
    • [public inheritance] + [protected member] = [protected member]
    • [public inheritance] + [private member] = [inaccessible member]
  • Protected Inheritance: The derived class has no access to the private data and private member functions of the base class. The public and protected member of the base class are inherited as the protected member of the derived class.
    • [protected inheritance] + [public member] = [protected member]
    • [protected inheritance] + [protected member] = [protected member]
    • [protected inheritance] + [private member] = [inaccessible member]
  • Private Inheritance: The derived class has no access to the private data and private member functions of the base class. The public and protected member of the base class are inherited as the private member of the derived class.
    • [private inheritance] + [public member] = [private member]
    • [private inheritance] + [protected member] = [private member]
    • [private inheritance] + [private member] = [inaccessible member]
#include <iostream>

class Base
{
private:
    int a;
protected:
    int b;
public:
    int c;
};

class DerivA: public Base
{
/* b is protected and c is public, a is not accessible!! */
public:
    void foo()
    {
        // a = 11; // wrong!! not accessible !!
        b = 22;
        c = 33;
    }
};


class DerivB: protected Base
{
/* b and c are protected, a is not accessible!! */
public:
    void foo()
    {
        // a = 11; // wrong!! not accessible !!
        b = 22;
        c = 33;
    }
};


class DerivC: private Base
{
/* b and c are private, a is not accessible!! */
public:
    void foo()
    {
        // a = 11; // wrong!! not accessible !!
        b = 22;
        c = 33;
    }
};


class DerivA2: public DerivA 
{
public:
    void bar()
    {
        // a = 11; // wrong!! not accessible !!
        b = 22;
        c = 33;
    }
};


class DerivB2: protected DerivB 
{
public:
    void bar()
    {
        // a = 11; // wrong!! not accessible !!
        b = 22;
        c = 33;
    }
};

class DerivC2: private DerivC 
{
public:
    void bar()
    {
        // a = 11; // wrong!! not accessible !!
        // b = 22;
        // c = 33;
    }
};


int main(void)
{
    // Base class
    Base base;
    // base.a = 10; // Error!!
    // base.b = 11; // protected member can not be accessed 
    base.c = 22;


    // first generation class
    DerivA da;
    DerivB db;
    DerivC dc;

    da.c = 20;
    // db.c = 20; // error
    // dc.c = 20; // error

    // second generation class
    DerivA2 da2;
    DerivB2 db2;
    DerivC2 dc2;

    da2.c = 30;
    // db2.c = 30;
    // da2.c = 30;

    return 0;
}

Constructor/Destructor Calling Order

  • The contructors of the bases classes must be called before the constructor of the derived class.
  • The constructors of the virtually inherited bases are called before those non-virtually inherited bases.
  • The constructors of the bases are called according to the declaration order.
  • The destructors are called in a reversed order.
#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base constructor called!!" << endl;
    }

    ~Base()
    {
        cout << "Base destructor called!!" << endl;
    }

};

class DerivA: public Base
{
public:
    DerivA()
    {
        cout << "DerivA constructor called!!" << endl;
    }

    ~DerivA()
    {
        cout << "DerivA destructor called!!" << endl;
    }
};

class DerivB: protected Base
{
public:
    DerivB()
    {
        cout << "DerivB constructor called!!" << endl;
    }

    ~DerivB()
    {
        cout << "DerivB destructor called!!" << endl;
    }
};

//class DerivC: private Base
class DerivC: private virtual Base
{
public:
    DerivC()
    {
        cout << "DerivC constructor called!!" << endl;
    }

    ~DerivC()
    {
        cout << "DerivC destructor called!!" << endl;
    }
};

class Deriv2: public DerivA, DerivB, DerivC
{
public:
    Deriv2()
    {
        cout << "Deriv2 constructor called!!" << endl;
    }

    ~Deriv2()
    {
        cout << "Deriv2 destructor called!!" << endl;
    }
};


int main(void)
{
    Base base;

    DerivA da;
    DerivB db;
    DerivC dc;

    Deriv2 d2;

    // Base* pBase = new Deriv2(); // Error: Base is an ambiguous base of Deriv2

    cout << endl << "*******" << endl << endl;
    return 0;
}

Multiple Inheritance and Virtual Inheritance

Edsko de Vries wrote a very good article about multiple inheritance and virtual inheritance.

Initialization List

  • Constant member data must be initialized in the initialization list. the only exception is that static integer type can be initialized when declaring it.
  • If a data member is a reference, then it must be initialized in the initialization list.
  • The following code shows why we need initialization list in C++.

    #include <iostream>
    
    class CLSA // class A
    {
    public:
        int a;
        CLSA() {}
        CLSA(int b):a(b) {}
    };
    
    class CLSB // class B
    {
    public:
        int a;
        CLSB() {}
        CLSB(int b):a(b) {}
    };
    
    class CLSC: public CLSB // class C derived from class B
    {
    public:
        int a;
        CLSA clsa;
        CLSC(int x, int y, int z):clsa(x),  CLSB(y), a(z) {}
    
        // if we don't have the initialization list, we can still 
        // initialize clsa and a but not the CLSB part of CLSC 
        /*   
        CLSC(int x, int y, int z)
        {
            clsa = CLSA(x);
            // CLSB(y);
            a = z;
        } */
    
        void print()
        {
            std::cout << "clsa.a  = " << clsa.a  << std::endl;
            std::cout << "CLSB::a = " << CLSB::a << std::endl;
            std::cout << "CLSC::a = " << a       << std::endl;
        }
    };
    
    int main ()
    {
        CLSC clsc(11, 22, 33);
        clsc.print();
        return 0;
    }
    

Formal Exception Declaration

C++ allows a list of exceptions to be given with the function declaration.

void foo() throw(int);           //throw out int exception
void foo() throw(int, char);     //throw out exception with int, char type
void foo() throw();              //not exception will be throw out
void foo() throw(...);           //any exception

Here is a good article about C++ Exception.

Static Member Data/Function

  • Static data members are not part of objects of a given class type; they are separate objects. As a result, the declaration of a static data member is not considered a definition. The data member is declared in class scope, but definition is performed at file scope. These static members have external linkage.
  • Locally defined class can not have a static data member.

    int main(void)
    {
        class CLS 
        {
            static int a;
            int b;
        };
    
        return 0;
    } 
    
  • Don't use the keyword static when defining static data member and member function. Only use it when declaring.

    #include <iostream>
    
    class Base
    {
    public:
        int b;
        static int a;
        static void fun();
    };
    
    static // error: remove static
    void Base::fun()
    {
        a++;
    }
    
    static // error: remove static
    int Base::a = 10;
    

C++-style Casts

static_cast|const_cast|dynamic_cast|reinterpret_cast<type>(expression)
  • static_cast is very similar to the general-purpose C-style cast except that static_cast can't remove constness from an expression.
  • const_cast is used to cast away the constness or volatileness of an expression.
  • dynamic_cast perform safe casts down or across an inheritance hierarchy. That is, you use dynamic_cast to cast pointers or references to base class objects into pointers or references to derived or sibling base class objects in such a way that you can determine whether the casts succeeded. Failed casts are indicated by a null pointer (when casting pointers) or an exception (when casting references). dynamic_cast requires the Run-Time Type Information (RTTI) to keep track of dynamic types.
  • reinterpret_cast

    This operator is used to perform type conversions whose result is nearly always implementation-defined. As a result, reinterpret_casts are rarely portable. The reinterpret_cast operator changes one data type into another. It should be used to cast between incompatible pointer types. The most common use of reinterpret_cast is to cast between function pointer types. Casting function pointers is not portable because C++ offers no guarantee that all function pointers are represented the same way, and in some cases such casts yield incorrect results.

    typedef void (*FuncPtr)();         // a FuncPtr is a pointer
                                       // to a function taking no
                                       // args and returning void
    FuncPtr funcPtrArray[10];          // funcPtrArray is an array
                                       // of 10 FuncPtrs
    int doSomething();
    // funcPtrArray[0] = &doSomething;     // error! type mismatch
    
    funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething); // this compiles
    

Koenig Look-up and Namespaces

Keonig look-up AKA Argument-dependent Lookup is an algorithm used in all the standard conforming compilers. The name of this algorithm is after Andrew Koenig, who devised it. The algorithm tells the compiler to look at not just the usual places such as local scope, but also the namespaces that contain the argument's type. in C++ which handle cases as shown in the example code below:

namespace NS 
{
  class A {};
  void foo(A);
}

NS::A a; // global object of NS::A class.

int main()
{
    foo(a); // NS::func is called without specifying the namespace 
}

Postfix Increment Is Not lvalue

The following code cannot be compiled using gcc, but can be compiled using g++, since ++a is a lvalue in C++. But a++ is not an lvalue.

#include <stdio.h>

int main(void)
{
    int a = 6;
    ++a = 17; // error happens if you compile it with C compiler
    // a++ = 17; // error happens if you compile it with C OR C++ compiler

    return 0;
}

Since a reference can be used as an lvalue, so prefix increment operator for an object should return a reference rather than an object. postfix increment operator should return an object.

Member Function Can Be a Template Function

#include <iostream>
/* member function can be a template function */
struct CLS
{
    template <typename T>
    void foo ()
    {
        T t = 10;
        std::cout << t*t << std::endl;
    }

    static const int N = 1111; // static const int can be initialized here!!
};

int main()
{
    CLS cls;
    cls.foo<int>();

    return 0;
}

Get the Length of an Array Using Template

C-sytle codes need an length parameter when passing array to a function unless that array has an ending symbol like c-string. Array is treated as a pointer when passed to a function.

#include <iostream>

using namespace std;

void print(const unsigned short * const p, const size_t n)
{
    cout << "sizeof(p): " << sizeof(p) << endl;
    for (size_t i = 0; i < n; ++i)
    {
        cout << p[i] << endl;
    }
}


int main()
{
    const unsigned short a[] = { 11, 22, 33, 44, 55 };
    cout << "sizeof(a): " << sizeof(a) << endl;
    cout << "sizeof(a[0]): " << sizeof(a[0]) << endl;
    print(a, sizeof(a) / sizeof(a[0]));

    return 0;
}

C++ has a new data type called reference. We can pass a reference to an array to a function.

#include <iostream>

using namespace std;

void print(const unsigned short (& r)[5])
{
    cout << "sizeof(r): " << sizeof(r) << endl;

    for (size_t i = 0; i < sizeof(r)/sizeof(r[0]); ++i)
    {
        cout << r[i] << endl;
    }
}


int main(void)
{
    const unsigned short a[] = { 11, 22, 33, 44, 55 };
    print(a);
}

You cannot define a reference that is associated with an array without definited size. To solve this problem, we can use template.

#include <iostream>

using namespace std;

template <typename T, size_t N>
void print(const T (& r)[N])
{
    for (size_t i = 0; i < N; ++i)
    {
        cout << r[i] << endl;
    }
}

int main()
{
    const int c[] = { 1, 2, 4, 8 };

    print(c);

    const double d[] = { 3.14159, 2.71828, .57722 };

    print(d);
}

Here is a template function that can extracts the size of an array. It works a little bit like traits.

template <typename T, size_t N> 
size_t length(const T (&r)[N])
{
    return N;
}

Redundent Compiled Copy of a Function

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值