Symbian C++ Coding Standards Quick Start
All successful software projects involving more than one person have a set of coding standards. Sometimes those standards are implicit, but the larger the project, the more likely they are to be written down explicitly. Contributions to Symbian itself should follow a very extensive set of coding standards and conventions (hosted on https://collab.symbian.nokia.com/home). However, these standards are too much for a new developer to learn all at once and may not all be appropriate for other projects intended to run on the platform.
This guide splits up some of the more important rules in the coding standards for native development, according to their purpose. All of the coding standards exist for a reason. The most important reason is to make code safe (in other words, to prevent crashes and memory leaks). The most common reason is to make code easier to maintain (which may be more robust to change or expansion, easier to understand, or simply consistent). Another valid reason for defining a coding standard is to improve the efficiency of the code. However, efficient can mean different things – we might be talking about reducing code footprint, dynamic memory consumption or CPU usage, or increasing the productivity of developers using an API. The requirements for improved efficiency often conflict with one another and result in trade-offs which change based on the hardware that the code is running on, so coding standards in this category should be evaluated more carefully and revised more often than the others.
The rules are further split by applicability. Some of the standards apply to all native code, some relate to C++ language features and others are specific to Symbian idioms.
To quickly understand formatting rules try looking at H and CPP file templates. Full list of templates for the variety of file types is here. The templates should be used when adding new files, but are also great as an illustration of what the code should look like.
Follow existing formatting conventions
If you are more geared towards fixing bugs, quickest way to start is to follow conventions already present in the code.
Refer to the full coding standards
Finally, Symbian Coding Standards and Conventions define large set of rules for code formatting. If in doubt, this page can be used as a reference.
Compile With No Warnings
Set your compiler to use the highest warning level and compile your code cleanly so it emits no warnings. If you do get warnings, make sure you understand why and then alter your code to remove them, even if the code appears to run correctly despite generating a warning.
Memory Allocation Failure
When memory is allocated on the heap, it is essential to determine whether the allocation succeeded or failed, and act accordingly. Either the leaving overload of operator new should be used (new(ELeave)), or the pointer returned from the call to allocate the memory should be checked against NULL. In future versions of the Symbian platform, throwing operator new will also be a suitable alternative.
No Side-Effects from Assertions
Assertion statements should not perform ‘side effects’, for example, they should not modify the value of a variable.
C++-style casts should be used in preference to C-style casts. For example, static_cast<type>(expression) should be used in preference to (type)(expression). Help the compiler to help you!
Note: The Symbian OS macros for the C++ cast operators (for example, STATIC_CAST(type, expression)) are deprecated.
When de-referencing a pointer in a destructor, the validity of the pointer should first be checked in case it has not been initialized or it has already been destroyed by another destructor in the inheritance hierarchy.
Pointers that are deleted outside of a destructor that can later be accessed (for example, member variable pointers) should be set to NULL or immediately re-assigned.
Resource handles and heap-based member data should be cleaned up in a destructor if they are owned by a class.
A destructor should not be able to leave or to throw any type of C++ exception.
Strings and Buffers
Proven string classes like the Symbian OS descriptor classes should be used in preference to native strings and memory buffers or alternative, hand-crafted string classes. Classes provided by the Qt application framework (QString and QByteArray) are suitable alternatives and classes provided by the STL will be suitable in future versions of the platform when used in conjunction with a throwing operator new. Be extremely careful when dealing with C-style strings and arrays.
Symbian C++ Only
Multiple inheritance should be used only through interfaces defined by M classes. Inherit from one CBase-derived class and one or more M classes (the CBase-derived class should appear first in the inheritance list to ensure that destruction by the cleanup stack occurs correctly).
The code inside a C++ constructor should not leave.
Construction and initialization code that is non-trivial and could fail should be implemented in the second phase of a two-phase constructor, typically in a ConstructL() method.
The cleanup stack should be used to avoid orphaning of memory or resources in the event of a leave.
If an object is being deleted, it should not be on the cleanup stack.
An object should not be simultaneously owned by another object and on the cleanup stack. This means that class member data should never be placed on the cleanup stack if it is also destroyed in the class destructor, as is usually the case.
Functions with a ‘C’ suffix on their name (such as NewLC()) automatically put a pointer to the object(s) they allocate on the cleanup stack. Therefore, those objects should not also be pushed on to the cleanup stack explicitly or they will be present twice.
A TRAP may be used within a destructor of a class stored on the heap, but it is not safe to use a TRAP within any destructor that may be called as the stack unwinds.
When calling code that can throw a C++ exception, catch everything and convert the exceptions to leaves. TRAP and TRAPD will panic if they receive a C++ exception which was not one raised by a Symbian leave (of type XLeaveException).
Ease of Maintenance
A header file should be ‘self-sufficient’. It should compile on its own, and include any headers that its contents depend upon. Code that includes the header should not also have to include others in order to compile.
A header file should not include more headers than strictly necessary. Where appropriate, prefer the use of forward references to reduce the number of inclusions.
Include guards should be used to protect against multiple inclusions of headers. For example:
... // Header file code goes here
#endif // GAME_ENGINE_H__
Explicit comparison should be used when testing integers: use if(aLength!=0) rather than if(aLength).
Hard-coded ‘magic’ numbers should be avoided. Constants or enumerations should be preferred instead.
In switch statements, the use of fall-through in a case statement should be commented to convey the intent. A switch statement should also always contain a default statement, even if this does nothing but assert that it is never reached.
Use and Comment Assertions
Use the __ASSERT_DEBUG assertion macro liberally to detect programming errors. It may sometimes also be appropriate to use __ASSERT_ALWAYS to catch invalid runtime input.
Assertion statements should be explained using a comment in the code to document the assumptions they make and render them easier to maintain.
Class and Function Design
Enumerations should be scoped within the class or namespace to which they are relevant, and should not be global, unless they are required to be, to avoid polluting the global namespace.
In class design, overloaded functions should be used in preference to default parameters.
Private inheritance should be avoided except in rare cases. Prefer to use composition instead.
The inline keyword should be specified explicitly for those functions that should be inlined. Note, that functions defined within the class declaration are implicitly inline. If this is intended - it is easier to write, but it's easier to read, and hence maintain, if you declare them as inline and define them separately in the header or *.inl file. Note: the compiler may, or may not decide to expand the function inline in either case.
In functions, reference parameters should generally be used in preference to pointers, except where ownership is transferred or if it is valid for the parameter to be NULL. In those cases, a pointer must be used.
In functions, the const specifier should always be used for parameters and return values if the data is not intended to be modified.
Private functions should only be exported if:
- they are accessed by public inline members
- they are virtual and the class is intended to be derived from in code which is not part of the exporting module.
Symbian C++ Only
Follow the Symbian OS naming conventions. If all members of a team follow the same naming conventions in their code, it can be shared and maintained more easily, because it is immediately understandable. This also applies to code that has been created by a completely different team, such as a utility library or the SDKs for working on Symbian itself. Symbian and its licensees use a specific set of rules when naming classes, variable and function names, so that those using the development kits can easily understand how to use the APIs and example code provided.
The concrete types defined in e32def.h should be used in lieu of the language-defined basic types. For example, TInt should be used instead of int.
M classes should be used to specify the interface only, not the implementation, and should generally contain only pure virtual functions.
When using descriptors as function parameters, the base classes (TDes and TDesC) should be used, and should be passed by reference.
Handling Date and Time Values
The Symbian OS date and time classes (such as TDateTime and TTime) should be used instead of hand-crafted alternatives.
Static Factory Functions
Where two-phase construction is used, one or more public static factory functions (typically called NewL() and NewLC()) should be provided for object construction. The construction methods themselves should be private or protected.
Check Correct Cleanup
The checking methods of CleanupStack::Pop() and CleanupStack::PopAndDestroy() should be used where possible, to check that PushL() and Pop() calls are balanced.
In functions, pass by value should only be used for basic data types or stack-based objects (such as structures or Symbian OS T-class objects) that are small (<8 bytes). Pass-by reference (or pointer in C) should be used for larger objects (≥8 bytes). Also the number of function parameters should be as small as possible.
When testing pointer values for NULL, it is unnecessary to use explicit comparison, and you should prefer to use if(ptr) over if(ptr!=NULL), unless writing the comparison in full clarifies the intent of the code.
Minimize Stack Use
Stack use should be minimized as much as is possible, for example, use the heap to store large data structures instead of the stack and take care when declaring larger stack-based variables within loops or recursive functions. Use of the TFileName and TParse classes is a common cause of stack consumption, because objects of both types are over 512 bytes in size (remember that the default stack size is only 8KB).
Avoid Writable Global Data in DLLs
The use of writable global data should be avoided, where possible, in DLLs. Note: there are also issues with this in both the emulator and the free GCCE toolchain that make this good advice, beyond the small efficiency gain.
No Inline Exports
Inline functions should never be exported regardless of whether they are public, protected or private.
For efficiency, when initializing member variables in a constructor, the use of member initialization lists should be preferred over assignment statements.
The member data of a C class does not require explicit initialization to zero.
Use Thin Templates
Thin templates should be used for templated code, to ensure the smallest possible code size while still gaining the benefits of C++ templates.
Symbian C++ Only
The _L macro for literal descriptors should not be used except where it is convenient to do so in test code. Prefer to use the _LIT macro for literal descriptors.
Consistent use of these coding standards can make your code safer, easier for others to understand and maintain, and also more efficient. You can decide on a subset of these to apply to any new project but also refactor existing code to incorporate them. In both cases the quality of your projects should improve.
For code submitted to Symbian itself, there is more more extensive set of Coding Standards and Conventions that must be adhered to.
If you found this useful then you can get more advice for good coding, complete with explanations for standards in: C++ Coding Standards: 101 Rules, Guidelines and Best Practices by Herb Sutter and Andrei Alexandrescu (2005). Pearson Education, Inc.