Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

19

Thin Templates

Now, now, my good man, this is no time to be making enemies

Voltaire on his deathbed, in response to a priest who asked him to renounce Satan

C++ templates are useful for code that can be reused with different types, for example to implement container classes such as dynamic arrays. Templates allow the code to be generic, accepting any type, without forcing the programmer to overload a function.

template<class T>

class CDynamicArray : public CBase

{

public:

... // Functions omitted for clarity void Add(const T& aEntry);

T& operator[](TInt aIndex); };

Prior to the introduction of templates to the C++ standard, generic code tended to be written using void* arguments, to allow the caller to specify any pointer type as an argument. However, the major benefit of using C++ templates for genericity, instead of void*, is that templated code can be checked for type safety at compile time.

However, the problem with template code is that it can lead to a major increase in code size, because for each type used in the template, separate code is generated for each templated function. For example, if your code used the CDynamicArray class above for both an array of HBufC* and an array of TUid values, the object code generated when your code was compiled would contain two copies of the Add() function and two copies of operator[], one for an array of HBufC* and one for an array of TUid. What’s more, the template code is generated, at best, once for each DLL or EXE that uses it and, at worst, for every compilation unit. Compiling a templated class into a binary can thus have a significant impact on its size.

294

THIN TEMPLATES

The advantage of automatically generated template code is thus a disadvantage when code size matters. Because Symbian OS code is deployed on mobile phones with limited ROM and RAM sizes, it is important to avoid code bloat. Using templated classes, such as CDynamicArray above, expands the code size too significantly. To avoid this, but still reap the benefits of C++ templates, Symbian OS makes use of the thin template pattern. This chapter describes the pattern in more detail, so you have a good idea of how the system code works. If you intend to use C++ templates1 in your code on Symbian OS, you should endeavor to use this pattern to minimize code size. Let’s consider the theory first, and then look at a couple of examples of the use of thin templates in Symbian OS code.

The thin template idiom works by implementing the necessary code logic in a generic base class that uses type-unsafe TAny* pointers rather than a templated type. This code is liable to misuse because of the lack of type-checking, so typically these methods will be made protected in the base class to prevent them being called naively. A templated class will then be defined which uses private inheritance2 from this class to take advantage of the generic implementation. The derived class declares the interface to be used by its clients and implements it inline in terms of the base class. I’ll show an example of this from Symbian OS code shortly.

Since the derived class uses templates, it can be used with any type required. It is type-safe because it is templated and thus picks up the use of an incorrect type at compile time. There are no additional runtime costs because the interfaces are defined inline, so it will be as if the caller had used the base class directly. And importantly, since the base class is not templated, only a single copy of the code is generated, regardless of the number of types that are used.

For illustration purposes, here is just a small part of the RArrayBase class and its subclass RArray (from e32std.h and e32std.inl). The type-unsafe base class (RArrayBase) implements the code logic for the array but cannot be used directly because all its methods are protected. You’ll find a detailed discussion of the RArray class, and other Symbian OS container classes, in Chapter 7.

1 You will typically want to use templates when writing a class that manipulates several different types using the same generic code. The code should be agnostic about the type passed into the template parameter, that is, the underlying logic is independent of type. Typical examples of (thin) template classes in Symbian OS are the array classes (I’ll discuss RArray shortly), the singlyand doubly-linked list classes (based on TSglQueBase and TDblQueBase) and the circular buffers (based on CCirBufBase).

2 Private inheritance means that the derived class is implemented in terms of the base class. Private inheritance is used when the deriving class uses some of the implemented methods of the base class, but has no direct conceptual relationship with the base class. Using private inheritance allows implementation to be inherited but all the methods of the base class become private members of the deriving class. In effect, the deriving class does not inherit the interface of the base class.

THIN TEMPLATES

295

class RArrayBase

{

protected:

IMPORT_C RArrayBase(TInt anEntrySize);

IMPORT_C RArrayBase(TInt aEntrySize,TAny* aEntries, TInt aCount); IMPORT_C TAny* At(TInt anIndex) const;

IMPORT_C TInt Append(const TAny* anEntry);

IMPORT_C TInt Insert(const TAny* anEntry, TInt aPos);

...

};

The templated RArray class privately inherits the implementation and defines a clear, usable API for clients. The API is defined inline and uses the base class implementation. Elements of the array are instances of the template class.

template <class T>

class RArray : private RArrayBase

{

public:

...

inline RArray();

inline const T& operator[](TInt anIndex) const; inline T& operator[](TInt anIndex);

inline TInt Append(const T& anEntry);

inline TInt Insert(const T& anEntry, TInt aPos);

...

};

template <class T>

inline RArray<T>::RArray() : RArrayBase(sizeof(T)) {}

template <class T>

inline const T& RArray<T>::operator[](TInt anIndex) const {return *(const T*)At(anIndex); }

template <class T>

inline T& RArray<T>::operator[](TInt anIndex) {return *(T*)At(anIndex); }

template <class T>

inline TInt RArray<T>::Append(const T& anEntry) {return RArrayBase::Append(&anEntry);}

template <class T>

inline TInt RArray<T>::Insert(const T& anEntry, TInt aPos) {return RArrayBase::Insert(&anEntry,aPos);}

Use of the class is then straightforward:

void TestRArray()

{

const TInt arraySize = 3;

RArray<TInt> myArray(arraySize);

for (TInt index = 0; index<arraySize; index++)

{

296

THIN TEMPLATES

myArray.Append(index);

}

TInt count = myArray.Count();

ASSERT(arraySize==count);

for (index = 0; index<arraySize; index++)

{

ASSERT(myArray[index]==index);

}

}

For another example of the thin template pattern in Symbian OS, consider the TBufC and TBuf descriptor classes discussed in Chapter 5. These classes are templated on an integer, the value of which is used as the variable which determines the maximum statically-allocated length of the buffer. In this case, the inheritance model is public and the derived class publicly inherits the base class implementation (whereas the previous example used private inheritance to gain access to the implementation without inheriting the behavior).

The constructors of the derived TBufC16 class, and other functions that use the template parameter, are declared inline. I’ve shown the constructors for the base and derived classes below (from e32des16.h and e32std.inl):

class TBufCBase16 : public TDesC16

{

protected:

IMPORT_C TBufCBase16();

inline TBufCBase16(TInt aLength);

IMPORT_C TBufCBase16(const TUint16 *aString,TInt aMaxLength); IMPORT_C TBufCBase16(const TDesC16 &aDes,TInt aMaxLength);

...

};

template <TInt S>

class TBufC16 : public TBufCBase16

{

public:

inline TBufC16();

inline TBufC16(const TUint16 *aString); inline TBufC16(const TDesC16 &aDes);

...

};

template <TInt S>

inline TBufC16<S>::TBufC16() : TBufCBase16()

{}

template <TInt S>

inline TBufC16<S>::TBufC16(const TUint16 *aString) : TBufCBase16(aString,S)

{}

template <TInt S>

inline TBufC16<S>::TBufC16(const TDesC16 &aDes)

SUMMARY

297

: TBufCBase16(aDes,S)

{}

To get the benefits of C++ templates without the disadvantages of code bloat, you should prefer the thin-template idiom which is used throughout Symbian OS code.

19.1 Summary

This chapter explained why C++ templates are ideal for reusable typesafe but type-agnostic code, but have the disadvantage that they can increase their clients’ code size quite considerably. For each templated class, every time a different type is used, separate code is generated for every templated function. Furthermore, this code duplication occurs in each client DLL or compilation unit using the templated class. This can cause significant code bloat unless the number of different types that are used with the templated class is limited, the code generated is small, and it is guaranteed that only a few clients will use the templated class.

Many Symbian OS container classes use the thin template pattern to gain the advantages of C++ templates without the disadvantages of increased code size (which is unacceptable on the ”small footprint” devices on which Symbian OS is deployed).

This chapter described the characteristics of the thin template pattern, which typically defines a base class (containing a generic implementation, usually specified as protected to prevent it being called directly) and a derived class (which uses private inheritance to inherit the implementation of the base class). The derived class exposes a templated interface, implemented inline in terms of the base class and thus benefits from compile-time type-checking by using C++ templates. The derived class does not have the associated size overhead because, regardless of the number of types used, the code is inline and uses a single copy of the generic base class implementation.

In this chapter the RArray, TBuf and TBufC classes illustrated the thin template idiom as it is employed on Symbian OS, and how these classes should be used. The RArray class is discussed in detail in Chapter 7, while TBuf and TBufC are examined in Chapter 5.

20

Expose a Comprehensive and

Comprehensible API

Have no fear of perfection – you’ll never reach it

Salvador Dali

So, you’re ready to design a class. You’ve read Chapter 1, and have thought about the characteristics of your class, maybe by asking the following questions: ”How will objects of my class be instantiated?”; ”Will they be stackor heap-based?”; ”Will the class have any data or simply provide an interface?”; ”Will it need a destructor?” The answers to these questions will help you decide what kind of Symbian OS class it is and give you the first letter of its name, the rest of which is down to you. When choosing a class name it is frequently difficult to be descriptive yet brief. Unless your class consists solely of static functions, it will be instantiated and used as an object. The class name should be a noun to reflect that. I’ll discuss the best strategy for naming classes and their member data, methods and parameters, later in this chapter.

The type of class you choose affects, to some extent, the definition of the class. For example, if you decide to implement it as a T class, it won’t have a destructor nor own any data which needs one. On the other hand, if you’re writing a C class, you’ll most likely need to use two-phase construction. To do this, you will make constructors protected or private and provide a public static function, usually called NewL(), which calls a non-public second-phase construction method to perform any initialization that may leave. You’ll find more information about two-phase construction in Chapter 4.

This chapter aims to highlight the factors you need to consider when designing a class, in particular the application programming interface (API) it will expose to its clients. The chapter isn’t a comprehensive treatise in class design – that would need a whole book – but it does point out some of the more important points for good C++ class design on Symbian OS.