Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 69
Текст из файла (страница 69)
There are valid reasons why you may need more stack, but you mayalternatively wish to consider modifying the program architecture or employing some of thetips described here.326GOOD CODE STYLEthe size of parameters passed into recursive code and to move localvariables outside the code.Consider the Effect of Settings Data on Object SizeTo minimize object size where a class has a large amount of state orsettings data, you may be able to use bitfields to store it rather than32-bit TBools or enumerations (in fact, this applies equally to stack- andheap-based classes).
A good example of this is the TEntry class which ispart of F32, the filesystem module. A TEntry object represents a singledirectory entry on the filesystem, and keeps track of its attributes (suchas whether it is marked read-only or hidden), its name and its size. TheTEntry class is defined in f32file.h – it has a single TUint whichstores the various attributes, each of which is defined in the header fileas follows:const TUint KEntryAttNormal=0x0000;const TUint KEntryAttReadOnly=0x0001;const TUint KEntryAttHidden=0x0002;const TUint KEntryAttSystem=0x0004;const TUint KEntryAttVolume=0x0008;const TUint KEntryAttDir=0x0010;const TUint KEntryAttArchive=0x0020;...class TEntry{public:...IMPORT_C TBool IsReadOnly() const;IMPORT_C TBool IsHidden() const;IMPORT_C TBool IsSystem() const;IMPORT_C TBool IsDir() const;IMPORT_C TBool IsArchive() const;public:TUint iAtt;...};The iAtt attribute setting is public data, so you can perform your ownchecking, although the class also provides a set of functions for retrievinginformation about the attributes of the file.
These are implementedsomething like the following:TBool TEntry::IsReadOnly() const{return(iAtt&KEntryAttReadOnly);}TBool TEntry::IsHidden() const{return(iAtt&KEntryAttHidden);}USE STACK MEMORY CAREFULLY327This is all quite straightforward and transparent to client code becausethe function returns a boolean value, although it doesn’t store the attributeas such. As I’ve mentioned previously, you should aim to keep your classintuitive to use and maintain. While you may be able to shave a few bytesoff the class by using bitfields, you need to consider the impact this mayhave on the complexity of your code, and that of your clients.Use Scoping to Minimize the Lifetime of Automatic VariablesSimple changes to the layout of code can make a significant difference tothe amount of stack used.
A common mistake is to declare all the objectsrequired in a function at the beginning, as was traditional in C code.Consider the following example, which is flawed because it declaresthree large objects on the stack before they are all immediately necessary.void CMyClass::ConstructL(const TDesC& aDes1, const TDesC& aDes2){TLarge object1;TLarge object2;TLarge object3;object1.SetupL(aDes1);object2.SetupL(aDes2);object3 = CombineObjectsL(object1, object2);iUsefulObject = FinalResultL(object3);}If either of the SetupL() functions fails, space will have been allocated and returned to the stack unnecessarily for at least one of the largeobjects. More importantly, once object1 and object2 have been combined to generate object3, it is unnecessary for them to persist throughthe call to FinalResultL(), which has an unknown stack overhead.By prolonging the lifetime of these objects, the code is wasting valuablestack space, which may prevent FinalResultL() from obtaining thestack space it requires.A better code layout is as follows, splitting the code into two separatefunctions to ensure the stack space is only used as it is required, at theexpense of an extra function call.
Note that, because the parameters areall passed by reference, no additional copy operations are performed topass them to the separate function.void CMyClass::DoSetupAndCombineL(const TDesC& aDes1,const TDesC& aDes2, TLarge& aObject){TLarge object1;object1.SetupL(aDes1);TLarge object2;object2.SetupL(aDes2);328GOOD CODE STYLEaObject = CombineObjectsL(object1, object2);}void CMyClass::ConstructL(const TDesC& aDes1, const TDesC& aDes2){TLarge object3;DoSetupAndCombineL(aDes1, aDes2, object3);iUsefulObject = FinalResultL(object3);}In general, it is preferable to avoid putting large objects on thestack – where possible you should consider using the heap instead.If you make large objects member variables of C classes, you canguarantee that they will always be created on the heap.21.4Eliminate Sub-Expressions to Maximize CodeEfficiencyIf you know that a particular function returns the same object or valueevery time you call it, say in code running in a loop, it is sensibleto consider whether the compiler will be able detect it and make anappropriate optimization.
If you can guarantee that the value will notchange, but cannot be sure that the compiler will not continue to callthe same function or re-evaluate the same expression, it is worth doingyour own sub-expression elimination to generate smaller, faster code.For example:void CSupermarketManager::IncreaseFruitPrices(TInt aInflationRate){FruitSection()->UpdateApples(aInflationRate);FruitSection()->UpdateBananas(aInflationRate);...FruitSection()->UpdateMangoes(aInflationRate);}In the above example, you may know that FruitSection() returnsthe same object for every call made in the function. However, thecompiler may not be able to make that assumption and will generate aseparate call for each, which is wasteful in terms of space and size.
Thefollowing is more efficient, because the local variable cannot change andcan thus be kept in a register rather than being evaluated multiple times:void CSupermarketManager::IncreaseFruitPrices(TInt aInflationRate){ELIMINATE SUB-EXPRESSIONS TO MAXIMIZE CODE EFFICIENCY329CFruitSection* theFruitSection = FruitSection();theFruitSection->UpdateApples(aInflationRate);theFruitSection->UpdateBananas(aInflationRate);...theFruitSection->UpdateMangoes(aInflationRate);}Consider the following example of inefficient code to access andmanipulate a typical container such as an array. The function adds aninteger value to every positive value in the iContainer member of classCExample (where iContainer is a pointer to some custom containerclass which stores integers).void CExample::Increment(TInt aValue){for (TInt index=0;index<iContainer->Count();index++){if ((*iContainer)[index]>0)(*iContainer)[index]+=aValue;}}The compiler cannot assume that iContainer is unchanged duringthis function.
However, if you are in a position to know that the functionsyou are calling on the container will not change it (that is, that Count()and operator[] do not modify the container object itself), it wouldimprove code efficiency to take a reference to the container, since thiscan be stored in a register and accessed directly.void CExample::Increment(TInt aValue){CCustomContainer& theContainer = *iContainer;for (TInt index=0;index<theContainer.Count();index++){if (theContainer[index]>0)theContainer[index]+=aValue;}}Another improvement in efficiency would be to store a copy of thenumber of objects in the container rather than call Count() on eachiteration of the loop. This is not assumed by the compiler, because itcannot be certain that the number of objects in iContainer is constantthroughout the function.
You, the programmer, can know if it is a safeoptimization because you know that you are not adding to or removingfrom the array in that function.(Rather than creating another variable to hold the count, I’ve simplyreversed the for loop so it starts from the last entry in the container. Thishas no effect on the program logic in my example, but if you know you330GOOD CODE STYLEhave to iterate through the contents of the array in a particular order, youcan just create a separate variable to store the count, which is still moreefficient than making multiple calls to retrieve the same value.)void CExample::Increment(TInt aValue){CCustomContainer& theContainer = *iContainer;for (TInt index=theContainer.Count();--index>=0;){if (theContainer[index]>0)theContainer[index]+=aValue;}}In the code that follows, I’ve added another optimization, whichstores the integer value retrieved from the array from the first call tooperator[], rather than making two separate calls to it.
This is anoptional enhancement, which probably depends on the percentage ofthe array to which the if statement applies. As a general rule, I tend tostore retrieved values if they are used twice or more, when I know theyare constant between uses.void CExample::Increment(TInt aValue){CCustomContainer& theContainer = *iContainer;for (TInt index=theContainer.Count();--index>=0;){TInt& val = theContainer[index];if (val>0)val+=aValue;}}21.5Optimize LateThe sections above offer a number of suggestions to improve the styleof your code, concentrating on reducing memory usage and code sizeand increasing efficiency.
You should bear them in mind, bringing themnaturally into your code ”toolbox” to make your Symbian OS codemore effective.However, a note of caution: don’t spend undue amounts of timeoptimizing code until it is finished and you are sure it works. Of course,you should always be looking for an optimal solution from the start,using good design and writing code which implements the design whilebeing simple to understand and maintain.
A typical program spends mostof its time executing only a small portion of its code. Until you haveSUMMARY331identified which sections of the code are worthy of optimization, it isnot worth spending extra effort fine-tuning the code. When you haveprofiled your code, you will know what to optimize and can consider thetips above. Then is the time to take another look at the code and see ifthe general rules I’ve mentioned here (minimizing stack usage, releasingheap memory early and protecting it against leaks, and coding efficientlywhen the compiler cannot make assumptions for you) can be applied.And finally, you may find it useful to look at the assembler listing ofyour code.
You can generate this using the Symbian OS command lineabld tool, the use of which will be described in detail in your SDKdocumentation.abld listing generates an assembler listing file for the source filesfor a project. For a project with a single source file, testfile.cpp,abld listing winscw udeb creates an assembler listing file (testfile.lst.WINSCW) for the code in testfile.cpp.
You can inspectthis to find out which optimizations have been applied by the compiler,before coding the optimization yourself. The listing will also give you anidea of which code idioms are particularly ”expensive” in terms of thenumber of instructions required.21.6 SummaryTo program effectively on Symbian OS, your code needs to be robust,efficient and compact. While many of the rules for writing high qualityC++ are not specific to Symbian OS, this chapter has focused on a fewguidelines which will definitely improve code which must work withlimited memory and processor resources and build with a small footprintto fit onto a ROM.
The chapter reviewed some of the main areas coveredby the Symbian OS coding standards and described the main principlesthat developers working on Symbian OS adhere to, namely minimizingcode size, careful memory management for both stack and heap memoryand efficiency optimization.You can find more advice on writing good C++ in the books listed inthe Bibliography.AppendixCode ChecklistThe following list is based on the internal Symbian Peer Review Checklist.Declaration of Classes• A class should have a clear purpose, design and API.• An exported API class should have some private reserved declarationsfor future backward compatibility purposes.• The API should provide the appropriate level of encapsulation, i.e.member data hiding.• Friendship of a class should be kept to a minimum in order to preserveencapsulation.• Polymorphism should be used appropriately and correctly.• A class should not have exported inline methods.• A non-derivable class with two-phase construction should make itsconstructors and ConstructL() private.• const should be used where possible and appropriate.• Overloaded methods should be used in preference to default parameters.• Each C class should inherit from only one other C class and haveCBase as the eventual base class.• Each T class should have only stack-based member variables.• Each M class should have no member data.334CODE CHECKLISTHeader Files• Header files should contain ”in-source” documentation of the purposeof each class.• The inclusion of other header files should be minimized by usingforward declaration if required.• Header files should include the correct #ifndef...#define...#endif directive to prevent multiple inclusion.• The number of IMPORT_C tags should match the correspondingEXPORT_C tags in the .cpp file.Comments• Comments should match the code.• Comments should be accurate, relevant, concise and useful.• Each class should have ”in-source” documentation of its purpose inheader file.• Each method should have ”in-source” documentation of its purpose,parameters and return values in the .cpp file.Constructors• A CBase-derived class does not need explicit initialization.• Member data of T and R classes should be explicitly initializedbefore use.• Member data should be initialized in the initializer list as opposed tothe body.• A C++ constructor should not call a method that may leave.• Two-phase construction should be used if construction can fail withanything other than KErrNoMemory.• NewL() and NewLC() methods should wrap allocation and construction where necessary.CODE CHECKLIST335Destructors• All heap-allocated member data owned by the class should bedeleted.• There should be a NULL pointer check before any dereference of amember pointer.• Member pointers do not need to be set to NULL after deletion inthe destructor.• Member pointers deleted outside the destructor should be set to NULLor immediately re-assigned another value.• A destructor should not leave.Allocation and Deletion• For a new expression, either use new (ELeave), or check the returnvalue against NULL if leaving is not appropriate.• For a call to User::Alloc(), either use AllocL(), or check thereturn value against NULL if leaving is not appropriate.• Objects being deleted should not be on the cleanup stack or referencedby other objects.• C++ arrays should be de-allocated using the array deletion operator,delete [] <array>.• Objects should be de-allocated using delete <object>.• Other memory should beUser::Free (<memory>).de-allocatedusingthemethodCleanup Stack and Leave Safety• If a leaving method is called during an object’s lifetime, then theobject should either be pushed on to the cleanup stack or be heldas a member variable pointer in another class that is itself leavesafe.336CODE CHECKLIST• An object should not simultaneously be owned by another object andheld on the cleanup stack when a leaving method is called.• Calls to CleanupStack::PushL() and CleanupStack::Pop()should be balanced (look out for functions that leave objects on thecleanup stack, such as NewLC()).• If possible, use the CleanupStack::Pop() and PopAndDestroy() methods which take pointer parameters, because theycatch an unbalanced cleanup stack quickly.• Where possible, use CleanupStack::PopAndDestroy() ratherthan Pop() followed by delete or Close().• Local R objects should be put on the cleanup stack using CleanupClosePushL(), usually after the object has been ”opened”.• Errors caught by a TRAP harness that are ignored must be clearlycommented and explained.• The code within a TRAP harness should be able to leave.• Error processing after a TRAP that ends with an unconditionalUser::Leave() with the same error code should be re-codedusing the cleanup stack.• Use the LeaveScan tool to check source code for functions that canleave but have no trailing L.Loops and Program Flow Control• All loops should terminate for all possible inputs.• The bracing should be correct on if.