Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 62
Текст из файла (страница 62)
You should aim toencapsulate member data privately within a class and provide (noninline) accessor functions where necessary. Chapter 20 discusses this inmore detail.Allow Object Initialization to LeaveThe steps required to instantiate an object of your class may not currentlyhave the potential to fail, but the class may be extended in future, perhapsto read data from a configuration file which may not succeed, say if thefile is corrupt or missing. To allow for an extension of this type, objectinstantiation should be able to leave safely should this become necessary.290COMPATIBILITYThis is a straightforward guideline to adhere to, because the SymbianOS ”two-phase construction” idiom, described in Chapter 4, allows forexactly this type of extensibility:class CExtensibleClass : public CBase{public:static CExtensibleClass* NewL();... // Omittedprotected:CExtensibleClass();// Constructor is externally inaccessiblevoid ConstructL(){}; // For extensibilityprivate:...
// Omitted};CExtensibleClass::CExtensibleClass(){// Implement non-leaving construction code in this function }CExtensibleClass* CExtensibleClass::NewL(){// Can later be extended to call ConstructL() if initialization// needs to leaveCExtensibleClass me = new (ELeave) CExtensibleClass();return (me);}Override Virtual Functions that You Expect to Override LaterThis will ensure that compatibility is not broken should you later need tooverride a virtual function which was originally inherited (as describedearlier in Section 18.4).
If you do not currently need to modify the functions beyond what the base class supplies, the overridden implementationshould simply call the base-class function. This will allow the functionsto be extended in future. Earlier, I gave the example of CSiamese,deriving from CCat, which inherited the default implementation of theCCat::SleepL() virtual method in version 1.0 but overrode it in version 2.0. The sample code below shows how to avoid this by overridingboth virtual functions in version 1.0, although CSiamese::SleepL()simply calls through to CCat::SleepL():Class CCat :{public:IMPORT_Cpublic:IMPORT_CIMPORT_C// ...};public CBase // Abstract base classvirtual ∼CCat() =0;virtual void EatL(); // Default implementationvirtual void SleepL();// Default implementationclass CSiamese : public CCat{// Version 1.0COMPATIBILITY AND THE SYMBIAN OS CLASS TYPES291IMPORT_C virtual ∼CSiamese();public:IMPORT_C virtual void EatL(); // Overrides base class functionsIMPORT_C virtual void SleepL();// ...};// Function definitions not relevant to the discussion have been// omittedvoid CSiamese::EatL(){// Overrides base class implementation...
// Omitted for clarity}void CSiamese::SleepL(){// Calls base class implementationCCat::SleepL();}Provide ”Spare” Member Data and Virtual FunctionsAs you’ll have gathered from a number of the guidelines above, the useof virtual functions, although powerful in a C++ sense, can be quitelimited in terms of extensibility when compatibility must be maintained.If it is possible that your class may need to be extended (and this isoften hard to determine, so you may prefer to assume that it will), thenyou should add at least one reserve exported virtual function. This willgive you the means by which to extend the class without disrupting thevtable layout of classes which derive from the class.
In effect, thisprovides a means to get around the limits by which virtual functions maybe extended, through the use of explicit interface design. You should,as always, implement any reserved functions, defaulting them to performno action.By the same token, you may wish to reserve at least four extra bytesof private member data in classes which may later need to be extended.This reserved data can be used as a pointer to extra data as it is required.Of course, if the class is internal to your component, or is unlikelyto require later modification, you should not reserve extra memory inthe class object, in order to continue to minimize the use of limitedmemory resources.18.7 Compatibility and the Symbian OS Class TypesChapter 1 describes the main class types, and their characteristics, onSymbian OS.
The discussion in this chapter has mainly focused oncompatibility issues which apply to C class objects and, to a lesser extent,R classes. T classes, by their very nature, tend to be stack-based and areoften simple, taking a similar role to a C struct. T classes frequently292COMPATIBILITYdo not have constructors and never have destructors. Once a T class ispublicly defined, it is often difficult to make client-compatible changesto it.18.8SummaryA change is acceptable if every line of code affected by it can be altered,where necessary, and the code rebuilt against the changes. In effect,this often means that a change must be restricted to the internals ofa component rather than its public API.
For this reason, you shouldendeavor to keep private definitions and header files (and anything elsewhich is likely to be subject to change) out of the public domain.A change is also acceptable if it can be verified as compatible, according to the guidelines I’ve discussed here. In general, the key compatibilityissue for shared library DLLs is backward binary compatibility, withsource compatibility an additional aim.These guidelines derive from those used within Symbian, in accordance with the C++ standard.
I am very grateful to David Batchelor, JohnForrest and Andrew Thoelke of Symbian who have previously writtenguides to compatibility best practice on Symbian OS, which I used as thebasis of this chapter.19Thin TemplatesNow, now, my good man, this is no time to be making enemiesVoltaire on his deathbed, in response to a priest who asked him torenounce SatanC++ 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, withoutforcing the programmer to overload a function.template<class T>class CDynamicArray : public CBase{public:... // Functions omitted for clarityvoid Add(const T& aEntry);T& operator[](TInt aIndex);};Prior to the introduction of templates to the C++ standard, genericcode tended to be written using void* arguments, to allow the caller tospecify any pointer type as an argument.
However, the major benefit ofusing C++ templates for genericity, instead of void*, is that templatedcode can be checked for type safety at compile time.However, the problem with template code is that it can lead to amajor increase in code size, because for each type used in the template,separate code is generated for each templated function. For example, ifyour code used the CDynamicArray class above for both an array ofHBufC* and an array of TUid values, the object code generated whenyour code was compiled would contain two copies of the Add() functionand two copies of operator[], one for an array of HBufC* and one foran 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 compilationunit. Compiling a templated class into a binary can thus have a significantimpact on its size.294THIN TEMPLATESThe advantage of automatically generated template code is thus adisadvantage when code size matters. Because Symbian OS code isdeployed on mobile phones with limited ROM and RAM sizes, itis important to avoid code bloat. Using templated classes, such asCDynamicArray above, expands the code size too significantly. Toavoid this, but still reap the benefits of C++ templates, Symbian OSmakes use of the thin template pattern. This chapter describes the patternin 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, youshould endeavor to use this pattern to minimize code size.
Let’s considerthe theory first, and then look at a couple of examples of the use of thintemplates in Symbian OS code.The thin template idiom works by implementing the necessary codelogic in a generic base class that uses type-unsafe TAny* pointers ratherthan a templated type. This code is liable to misuse because of the lack oftype-checking, so typically these methods will be made protected in thebase class to prevent them being called naively. A templated class willthen be defined which uses private inheritance2 from this class to takeadvantage of the generic implementation.
The derived class declares theinterface to be used by its clients and implements it inline in terms of thebase 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 typerequired. It is type-safe because it is templated and thus picks up the useof an incorrect type at compile time. There are no additional runtimecosts because the interfaces are defined inline, so it will be as if the callerhad used the base class directly. And importantly, since the base class isnot templated, only a single copy of the code is generated, regardless ofthe number of types that are used.For illustration purposes, here is just a small part of the RArrayBaseclass and its subclass RArray (from e32std.h and e32std.inl).