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

330

GOOD CODE STYLE

have to iterate through the contents of the array in a particular order, you can just create a separate variable to store the count, which is still more efficient 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, which stores the integer value retrieved from the array from the first call to operator[], rather than making two separate calls to it. This is an optional enhancement, which probably depends on the percentage of the array to which the if statement applies. As a general rule, I tend to store retrieved values if they are used twice or more, when I know they are 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 Late

The sections above offer a number of suggestions to improve the style of your code, concentrating on reducing memory usage and code size and increasing efficiency. You should bear them in mind, bringing them naturally into your code ”toolbox” to make your Symbian OS code more effective.

However, a note of caution: don’t spend undue amounts of time optimizing 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 while being simple to understand and maintain. A typical program spends most of its time executing only a small portion of its code. Until you have

SUMMARY

331

identified which sections of the code are worthy of optimization, it is not worth spending extra effort fine-tuning the code. When you have profiled your code, you will know what to optimize and can consider the tips above. Then is the time to take another look at the code and see if the general rules I’ve mentioned here (minimizing stack usage, releasing heap memory early and protecting it against leaks, and coding efficiently when the compiler cannot make assumptions for you) can be applied.

And finally, you may find it useful to look at the assembler listing of your code. You can generate this using the Symbian OS command line abld tool, the use of which will be described in detail in your SDK documentation.

abld listing generates an assembler listing file for the source files for 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 inspect this to find out which optimizations have been applied by the compiler, before coding the optimization yourself. The listing will also give you an idea of which code idioms are particularly ”expensive” in terms of the number of instructions required.

21.6 Summary

To program effectively on Symbian OS, your code needs to be robust, efficient and compact. While many of the rules for writing high quality C++ are not specific to Symbian OS, this chapter has focused on a few guidelines which will definitely improve code which must work with limited memory and processor resources and build with a small footprint to fit onto a ROM. The chapter reviewed some of the main areas covered by the Symbian OS coding standards and described the main principles that developers working on Symbian OS adhere to, namely minimizing code size, careful memory management for both stack and heap memory and efficiency optimization.

You can find more advice on writing good C++ in the books listed in the Bibliography.

Appendix

Code Checklist

The 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 declarations for 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 preserve encapsulation.

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 its constructors 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 have CBase as the eventual base class.

Each T class should have only stack-based member variables.

Each M class should have no member data.

334

CODE CHECKLIST

Header Files

Header files should contain ”in-source” documentation of the purpose of each class.

The inclusion of other header files should be minimized by using forward 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 corresponding EXPORT_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 in header 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 initialized before use.

Member data should be initialized in the initializer list as opposed to the body.

A C++ constructor should not call a method that may leave.

Two-phase construction should be used if construction can fail with anything other than KErrNoMemory.

NewL() and NewLC() methods should wrap allocation and construction where necessary.

CODE CHECKLIST

335

Destructors

All heap-allocated member data owned by the class should be deleted.

There should be a NULL pointer check before any dereference of a member pointer.

Member pointers do not need to be set to NULL after deletion in the destructor.

Member pointers deleted outside the destructor should be set to NULL or immediately re-assigned another value.

A destructor should not leave.

Allocation and Deletion

For a new expression, either use new (ELeave), or check the return value against NULL if leaving is not appropriate.

For a call to User::Alloc(), either use AllocL(), or check the return value against NULL if leaving is not appropriate.

Objects being deleted should not be on the cleanup stack or referenced by 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 be de-allocated using the method

User::Free (<memory>).

Cleanup Stack and Leave Safety

If a leaving method is called during an object’s lifetime, then the object should either be pushed on to the cleanup stack or be held as a member variable pointer in another class that is itself leavesafe.

336

CODE CHECKLIST

An object should not simultaneously be owned by another object and held 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 the cleanup stack, such as NewLC()).

If possible, use the CleanupStack::Pop() and PopAndDestroy() methods which take pointer parameters, because they catch an unbalanced cleanup stack quickly.

Where possible, use CleanupStack::PopAndDestroy() rather than 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 clearly commented and explained.

The code within a TRAP harness should be able to leave.

Error processing after a TRAP that ends with an unconditional User::Leave() with the same error code should be re-coded using the cleanup stack.

Use the LeaveScan tool to check source code for functions that can leave 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. . .else statements.

Optimize loops by using the clearest type (do. . .while or while) in each case.

There should be a break statement for each case of a switch statement – or a comment if a flow-through to the next case statement is meant.

A switch statement shouldn’t contain case statements that return from the function unless every case in that switch statement contains a return.

A switch statement should be used to indicate state control. Each case statement should contain only a small amount of code; large chunks of code should be moved into separate functions to maintain the readability of the switch statement.

CODE CHECKLIST

337

There should be a default in every switch statement.

Flow control statements should be used rather than extra boolean variables to manage program flow.

Program Logic

Stack usage should be reasonable, i.e. don’t store large objects on the stack.

Errors should propagate properly and not be changed or remapped to e.g. KErrGeneral.

Pointers should be manipulated and accessed correctly, i.e. check for invalid address access, pointer overflow, etc.

Bitmasks should be used properly (& to read, | to set, to reset).

Unexpected events and inputs should be handled gracefully, i.e. use error returns, leaves or panics appropriately.

Hard-coded ”magic” values should not be used.

Logging statements should be informative and in useful places.

Code should be efficient, e.g. do not copy things unnecessarily, do not create too many temporaries, do not use inefficient algorithms.

Casts should be in C++ style such as static_cast, reinterpret_cast or const_cast rather than in C style.

A cast should only be used where absolutely necessary and should be the correct type for the expression.

Descriptors

Descriptor character size should be correct, i.e. 8 bits for binary data and ASCII-like text; 16 bits for explicitly Unicode text; no suffix for normal text.

_L should not be used for literal data: use _LIT instead (except for test code, etc.).

Assignment to TPtr or TPtrC does not redirect the pointer; Set() is required for that purpose.

HBufC::Des() and TBufC::Des() are not required to use the object as an unwritable descriptor.

338

CODE CHECKLIST

HBufC::Des() and TBufC::Des() are not required for assignment: these classes define assignment operators.

Use AppendNum() or Num() to format a number instead of

TDes::AppendFormat() or Format().

Use Append() instead of TDes::AppendFormat() or Format() to concatenate strings.

Arguments to TDes::AppendFormat() or Format() should match the format of the string and the descriptor must be passed by pointer.

Use descriptor functions such as Find() and Locate() rather than re-implementing this functionality.

Iterating over a descriptor using operator[] is expensive; consider using C++ pointer arithmetic and the TDesC::Ptr() function instead.

Descriptors passed to asynchronous operations should persist until the operation completes, e.g. do not use a stack-allocated descriptor.

Containers

The least-derived type of container should be used in declarations.

The initial size and granularity of the container should be appropriate.

All owned objects in the container should be destroyed (or ownership passed) before the container is destroyed.

The appropriate container for the purpose should be used.

Thin templates should be used for any templated code.