www.4FipS.com]-      
[ FipS' NEWS ]-

 ABOUT FipS =
[ START HERE ]=
[ PROFILE ]-
[ GUESTBOOK ]-
[ FORUMS ]-

 COMPUTERS=
[ CODING ]-
[ FipS' CODE ]-
[ FSHUB ENG. ]-
[ OLD STUFF ]-

 ART & CG =
[ CG ART ]-
[ ARTWORKS ]-

 PHOTO =
[ * ABOUT ]-
[ SERIES ]-
[ SHOTS ]-
[ PANORAMAS ]-
[ MACROS ]-
[ TIMELAPSE ]-

C++ Tips

C++ --->
[ C++ Tips, rev. 2006-05   /   ]
[ C++ Collected Wisdom, rev. 2006-09 ]
[ C++ Smart Pointers, rev. 2005-01 ]
OOP / Pragmatic UML --->
[ UML Introduction, rev. 2006-06 ]
[ UML Class Diagrams, rev. 2006-06 ]

Visitor Map      
= LINKS 
-[ CODING ]
-[ C++ ]
-[ GAME DEV ]
-[ GRAPHICS ]
-[ MATH ]
-[ LIBS ]
-[ PYTHON ]
-[ MOBILE ]
-[ FREE TOOLS ]

-[ CG ART ]
-[ PHOTO ]
-[ LEARN ENG ]

-[ E FRIENDS ]
-[ SELECTED ]
-[ MISC ]

= DUMPED 
-[ 2010 ] [ 07 ]
-[ 2009 ] [ 06 ]
-[ 2008 ] [ 05 ]


C++ Tips : rev. 2006-05-19 by FipS

This place is devoted to collect my everyday C++ experience.

Tip #6 - Static polymorphism, Interfaces & CRTP, 2006-05-19

Modular systems usually involve some kind of interface abstraction. The most common approach involved is interfaces realized through ABC (Abstract Base Classes). On a certain level of abstraction it is the ideal solution, it offers dynamic binding, crossing DLL boundaries etc. The negative site of the approach is a small real-time overhead introduced by virtual function calls. Most of the time it is insignificant but deeper inside a module e.g. in the inner loops or inside an algorithm, the overhead could become significant.

In those situations especially where the types involved are known in the compile-time one should consider using of Static polymorphism through C++ templates instead of ABC. The example below shows a compile-time alternative to ABC. Moreover, CRTP (Curiously Recurring Template Pattern) idiom is introduced to retain common implementation in the base class (interface). Such a programming construct doesn't require virtual function calls so there is a good chance to inline some of the functions to boost performance.

#include <iostream>

template <typename T>
struct Interface
{
    void Polymorphic() const // compile-time polymorphic call
     { static_cast<const T *>(this)->PolymorphicImpl(); }
    void Common() const // common implementation
     { std::cout << "Common()\n"; }
};

struct Concrete1 : public Interface<Concrete1>
{
    void PolymorphicImpl() const // concrete implementation #1
     { std::cout << "Concrete1::Polymorphic()\n"; }
};

struct Concrete2 : public Interface<Concrete2>
{
    void PolymorphicImpl() const // concrete implementation #2
     { std::cout << "Concrete2::Polymorphic()\n"; }
};

template <typename TIfc>
void Polymorphic(const TIfc &Ifc)
{
    Ifc.Polymorphic(); // call through the interface
}

int main()
{
    Concrete1 c1;
    Concrete2 c2;
    
    Polymorphic(c1); // polymorphic call of concrete implementation #1
    c1.Common(); // call of common implementation
    
    Polymorphic(c2); // polymorphic call of concrete implementation #2
    c2.Common(); // call of common implementation
}

// Concrete1::Polymorphic()
// Common()
// Concrete2::Polymorphic()
// Common()


[ Comments here... ]

Tip #5 - TSInt2Type<> as a Compile-Time Code Switch, 2006-03-15

Today let me introduce you a simple but very useful C++ template - TSInt2Type. Originally it comes from Andrei Alexandrescu's - [ Modern C++ Design: Generic Programming and Design Patterns Applied ] book.

template <int n>
struct TSInt2Type
{
    enum { Val = n };
};

The template generates a distinct type for each distinct constant integral value passed /AA/. You can use this feature in situations whenever you want to perform a compile-time code switch based on a compile-time constant. The example below shows this technique. It's a fragment of a static type system. There is an interface class 'IObject' and an implementation class 'SObject'. The latter has a static function 'Create', which is indicated by a compile-time constant 'IsImpl = 1'. The type system represented by 'TSTypeInfo' then needs to decide whatever or not a certain type 'T' has the create function. At this point TSInt2Type comes to help.

The template provides a constant-based type through 'TSInt2Type<T::IsImpl>()' which resolves the active code branch defined by a couple of overloaded template functions. The important feature of the compiler is that a template function, which is never used, isn't compiled at all! So the only active code branch has to be compilable!

#include <iostream>

// THE MAGIC TEMPLATE, generates a type for a compile time constant passed
template <int n> struct TSInt2Type { enum { Val = n }; };

// requested function pointer
typedef struct IObject * (*TCreateFuncPtr)();

// overloaded template functions by a constant-based type
template <typename T>
TCreateFuncPtr ResolveCreateFunc(TSInt2Type<0>) { return 0; }
template <typename T>
TCreateFuncPtr ResolveCreateFunc(TSInt2Type<1>) { return &T::Create; }

// static type info
template <typename T>
struct TSTypeInfo
{
    TSTypeInfo()
    {
        // run-time switch below, isn't compilable for T = 'IObject' !!!
        //TCreateFuncPtr pFunc = T::IsImpl ? return &T::Create : 0;
        
        // >>> >>> >>>
        // COMPILE-TIME SWITCH compiles fine!
        TCreateFuncPtr pFunc = ResolveCreateFunc<T>(TSInt2Type<T::IsImpl>());
        // <<< <<< <<<
        
        std::cout << "0x" << pFunc << std::endl;
    }
};

struct IObject // abstract interface
{
    enum { IsImpl = 0 }; // compile-time constant
    static TSTypeInfo<IObject> m_Info; // 'IObject' type info
    virtual void Foo() const = 0;
};
TSTypeInfo<IObject> IObject::m_Info; // cpp

struct SObject : public IObject // implementation
{
    enum { IsImpl = 1 }; // compile-time constant
    static TSTypeInfo<SObject> m_Info; // 'SObject' type info
    static IObject * Create() { return new SObject; }
    virtual void Foo() const {}
};
TSTypeInfo<SObject> SObject::m_Info; // cpp

int main() {}

// 0x00000000 // the result of 'return 0' version
// 0x0041919A // the result of 'return &T::Create' version

* 2006-03-20 according to [ Q240871 ] there is a bug in MSVC 6 compiler: If all the template parameters are not used in function arguments or return type of a template function, the template functions are not overloaded correctly! Solution: Use dummy arguments to the function. You can find a workaround below:

// VC6 bug fixed version
template <typename T>
TCreateFuncPtr ResolveCreateFunc(TSInt2Type<0>, const T* = 0)
 { return 0; }
template <typename T>
TCreateFuncPtr ResolveCreateFunc(TSInt2Type<1>, const T* = 0)
 { return &T::Create; }


[ Comments here... ]

Tip #4 - Algorithms, Traits & Policies, 2005-11-13, rev. 2006-02-23

The example below shows a simple version of an algorithm using the technique of Traits and Policies. It is a STL-like generic way to allow user to strongly customize types and algorithms.

Traits brings an association between a concrete type and an extra type information. In the example below, a type 'unsigned char' is bound with the storage type 'int' (TStorage)', in other words, whenever the algorithm wants to know the apropriate storage type for 'unsigned char' it uses 'TSTraits' to find out.

Policies provide an interface, which is involved in the algorithm. In the example below, a policy is used to specify the operation ('summation' or 'counting'), which is applied to all items of the sequence.


#include <iostream>

template <typename TElem> // TRAITS: defines traits of an element type
struct TSTraits;

template <> // TRAIT: binds 'uchar' element type with 'int' storage type
struct TSTraits<unsigned char>
{
    typedef int TStorage;
    static TStorage Zero() { return 0; }
};

struct TSSumPolicy // POLICY: defines a 'sum' variant of the algorithm
{
    template <typename TStorage, typename TElem>
    static void Operation(TStorage &Storage, const TElem &Elem)
     { Storage += Elem; }
};

struct TSCountPolicy // POLICY: defines a 'count' variant of the algorithm
{
    template <typename TStorage, typename TElem>
    static void Operation(TStorage &Storage, const TElem &) { ++Storage; }
};

template<typename TElem, typename TPolicy, typename TTraits =
 TSTraits <TElem> >
struct TSAlgorithm // ALGORITHM: customized by traits & policies
{
    typedef typename TTraits::TStorage TStorage;
    static TStorage Execute(TElem *pBegin, TElem *pEnd)
    {
        TStorage Storage = TTraits::Zero();
        while(pBegin != pEnd)
         { TPolicy::Operation(Storage, *pBegin); ++pBegin; }
        return Storage;
    }
};

int main()
{
    unsigned char auArray[] = { 100, 110, 120, 130, 140 };
    
    // calc a sum of the array items using 'TSSumPolicy'
    int nSum = TSAlgorithm<unsigned char, TSSumPolicy>::Execute
     (&auArray[0], &auArray[5]);
    std::cout << "sum = " << nSum << std::endl;
    
    // calc a number of items of the array using 'TSCountPolicy'
    int nCount = TSAlgorithm<unsigned char, TSCountPolicy>::Execute
     (&auArray[0], &auArray[5]);
    std::cout << "count = " << nCount << std::endl;
    
    return 0;
}

// sum = 600
// count = 5



[ Comments here... ]

Tip #3 - 'std::for_each' and functor adapter 'std::mem_fun', transl. 2005-06-18

Nowadays it is hard to imagine an application written without using of the STL collections. The collections are typically used as a storage of various object instances or instance pointers. Sometimes it is need to invoke an operation over all stored instances. It is typically done by calling a member function in a loop.

However, STL offers more elegant and potentially efficient way to do that. The algorithm 'std::for_each' and functor adapter 'std::mem_fun' or 'std::mem_fun_ref' are used. The algorithm 'std::for_each' calls a user defined function in a range of iterators, functor adapter 'std::mem_fun...' transforms function call to the call of a member function of an object. The difference between 'std::mem_fun' and 'std::mem_fun_ref' is that the first one is used when a collection of object instance pointers is expected while the second one is used for collection of embeded instances. 'std::mem_fun...' can call a member function with no argument. With the help of another functor adapter 'std::bind2nd' calling of a member function with one argument can be reached. Note that current STL standard doesn't support more then one argument. The example below shows the technique.

Note. [ Boost ] library offers alternatives to 'std::mem_fun...' that can call member functoins without limitation of the number of arguments. For more information see Boost's [ Bind ] and [ Lambda ].

#include <stdio.h>

#include <vector>
#include <algorithm> // std::for_each
#include <functional> // std::mem_fun, std::mem_fun_ref, std::bind2nd

struct STest
{
    void Print() { printf("0x%08x\n", (size_t)this); }
    void PrintValue(int nValue) { printf("nValue = %d\n", nValue); }
};

int main()
{
    // vector of object instances
    std::vector<STest> Tests;
    Tests.push_back(STest());
    Tests.push_back(STest());
    Tests.push_back(STest());

    // vector of object instance pointers
    std::vector<STest *> TestPtrs;
    TestPtrs.push_back(&Tests[0]);
    TestPtrs.push_back(&Tests[1]);
    TestPtrs.push_back(&Tests[2]);
    
    printf("std::mem_fun_ref:\n");
    
    // no argument call
    std::for_each(Tests.begin(), Tests.end(),
     std::mem_fun_ref(&STest::Print));
     
    // 1 argument call
    std::for_each(Tests.begin(), Tests.end(),
     std::bind2nd(std::mem_fun_ref(&STest::PrintValue), 10));
     
    printf("\nstd::mem_fun:\n");
    
    // no argument call
    std::for_each(TestPtrs.begin(), TestPtrs.end(),
     std::mem_fun(&STest::Print));
     
    // 1 argument call
    std::for_each(TestPtrs.begin(), TestPtrs.end(),
     std::bind2nd(std::mem_fun(&STest::PrintValue), 10));
     
    return 0;
}

// std::mem_fun_ref:
// 0x00322c70
// 0x00322c71
// 0x00322c72
// nValue = 10
// nValue = 10
// nValue = 10
//
// std::mem_fun:
// 0x00322c70
// 0x00322c71
// 0x00322c72
// nValue = 10
// nValue = 10
// nValue = 10


[ Comments here... ]

Tip #2 - Member references and 'operator =', transl. 2005-04-16

In an effort to produce a robust code, there may occur member items in a form of references in our objects (it is seen in the example below 'm_rnValue'). Initialization of the items is possible only in the constructor. If we need to implement the assignment operator 'operator =' (for example STL collections require the operator to allow to store the objects in), there is a problem. Assignment in a way 'm_rnValue = RHS.m_rnValue' is not possible, it only copies a value of the item not the reference! (for the const references it is not possible at all). There is an interesting solution that allows to implement the assignment operator correctly in such situations. Firstly, the destructor is explicitly invoked then a new instance of the object is created at the same memory address (the placement new is used). The method is seen in the example below. Note that there are some limitations when it is used in inherited objects (it is important to implement the assignment operator properly in all the inherited objects).

class CTest
{
 public:
 
    CTest(const int &nValue): m_rnValue(nValue) {}
    CTest(const CTest &RHS) : m_rnValue(RHS.m_rnValue) {}
    CTest & operator = (const CTest &RHS)
    {
        if(this != &RHS)
        {
            // wrong !!! (for 'const' not possible at all)
            // m_rnValue = RHS.m_rnValue;
            
            this->CTest::~CTest();
            new (this) CTest(RHS);
        }
        return *this;
    }
 
 private:
 
    CTest();
    const int &m_rnValue;
};

int main()
{
    int nValue1 = 1;
    int nValue2 = 2;

    CTest Test1(nValue1); // Test1.m_rnValue = 1
    CTest Test2(nValue2); // Test2.m_rnValue = 2
    
    Test2 = Test1; // call 'operator ='; Test(1/2).m_rnValue = 1
    
    nValue1 = 3; // Test(1/2).m_rnValue = 3

    return 0;
}


[ Comments here... ]
Dec, 2004 - C++ Tips - (c) Filip STOKLAS (FipS) - [ www ][ Guest Book ]