Cleanly formatted class, structure, and enum definitions greatly affect the readability of source code.
const
The following coding standards apply to class, structure, and enum definitions.
Block Comment | Precede each definition with three empty lines and the block comment. |
Open Brace Location | Place the opening brace on the same line as the object type and name. |
Member Indentation | Indent class member method and data names to align one tab from the longest type name or at a minimum on column 29. |
Member Arguments | Align member function argument types and names. |
Public, Protected, Private | List public methods and data first, protected methods and data second, and
private methods and data third. Group all public members, all protected members,
and all private members. Do not indent the keywords public ,
protected , or private . |
Friend Declarations | List friend declarations last, and align the friend names. |
Member Grouping | Group class and structure members as appropriate. Group common or related methods and data. Use spaces to separate and delineate different groups. |
Static Members | Separate static class members from non-static members unless they are clearly related. |
The following example illustrates the class, structure, and enum coding standards.
// ***************************************************************************** // oaMyClass // ***************************************************************************** class oaMyClass : public oaMyBaseClass { public: oaMyClass(oaUInt4 intArg, oaFloat floatArg = 3.5f); ~oaMyClass(); oaUInt4 getFunc1() const; oaBoolean getFunc2() const; oaInt4 getFunc3(oaUInt4 arg1) const; const oaMyClass *getFunc4(oaBoolean arg1) const; void setFunc1(oaUInt4 argA, oaUInt4 argB) const; oaBoolean setFunc2() const; void setFunc3(oaUInt4 arg1) const; oaMyClass *setFunc4(oaBoolean arg1) const; void myMemberFunc1(oaUInt4 myIntArg); static const oaUInt4 findMyClass(oaUInt4 info); protected: void specFuncA(oaUInt4 argA) const; private: oaUInt4 m_data1; oaBoolean m_data2; oaMyClass2 *m_data3; static oaMyClass3 *s_listHead; friend class myFriendClass1; friend class myFriendClass1; }; // ***************************************************************************** // oaMyStruct // ***************************************************************************** struct oaMyStruct { oaUInt4 m_data1; oaBoolean m_data2; oaMyClass2 *m_data3; }; // ***************************************************************************** // oaMyEnum // ***************************************************************************** enum oaMyEnum { oacEnumVal1 = 0, oacLongEnumVal2 = 1, oacEnumVal1 = 2 };
Because class member functions and data names are relative to the class name, you do not need to include the class name in the member name.
For example, if a member function of the polygon
class creates a
copy of the polygon, it is redundant to give this member function a name like
copyPolygon()
, since it is obvious that it pertains to polygons.
A more appropriate name is simply copy()
.
Occasionally, a developer will want to create a local variable for a purpose similar (but not identical) to a variable declared in a class or base class. A typical example is an accessor function that is used to set the value of some member variable in the class. For example,
class oaMyClass { public: void set(oaUInt4 value); private: oaUInt4 value; static oaMyClass singleton; };The argument
value
holds the same data as the member variable value
but the same name cannot
be used in the implementation without the local variable value
hiding the member variable value
.
Frequently, the existence of a member named value
will not be immediately apparent to the developer
because it appears in a base class of the class that is being developed. To avoid these issues, prefix all
non-static member variables with m_
and prefix all static member variables with s_
. The previous
example should look like this:
class oaMyClass { public: void set(oaUInt4 value); private: oaUInt4 m_value; static oaMyClass s_singleton; };
Inline methods are valuable for encapsulating functionality and data while ensuring high-performance, but you must always demonstrate that inlining a function results in better performance. Virtual functions require an extra step to determine whether they can be inline. One design pattern that frequently calls for inline treatment is when base class virtual functions are declared as pure, and all derived classes are required to redefine them. It is often useful for the base class to provide a basic definition for these functions that can then be called by the redefined functions in the derived classes. An extra level of function call overhead can be avoided if these base class definitions are inline.
Private member functions of a class may be defined as inline in a .cpp
file only if they are intended to be used within that file and nowhere else. This is consistent with the principle of information hiding, which calls for not revealing implementation details (such as those found in the body of a member function) to any wider audience than necessary. Never include inline methods in the class definition.
Inline functions must be added to the appropriate .inl
file. The class definition file must not contain any member function implementation. To allow for member functions in two interdependent classes to be inlined, the inlined functions must be placed in the .inl
files and not in the header files.
For C++ development, classes are the preferable object for almost all development. However, a struct may be more appropriate if:
When developing systems using C++, it is appropriate to completely eliminate the use of global variables and global functions. Because global information is obviously necessary and useful, create the data by relating the global variables and functions to a particular class, then implementing them as static members of the class.
For example, to maintain a linked list of nets in a design, rather than keep
a global pointer to the head of the list, a more appropriate solution is to keep
the pointer as a static member of the net
class. Instead of the
code:
oaNet *oavFirstNet;
use a static member:
class oaNet { . . . private: . . . static oaNet *s_first; }
The pointer is hidden and protected but still accessible from the member
functions of the oaNet
class (which are the most likely and appropriate
functions to use the pointer anyway.)
Be careful when creating static class members. Like all global variables, static class members are not thread-safe, so proper safeguards must be used to protect them.
Static class member variable definitions appear at the head of .cpp
files in a separate block. The default rules for variable declarations apply, but avoiding alignment and placing a single space between the type and the variable name is also acceptable. Additionally, the initializer values may also be aligned. For example, the following is the preferred format:
// ***************************************************************************** // Initialize Static Members // ***************************************************************************** oaUInt4 oaDatabase::s_size = 16; const oaUInt4 oaDatabase::s_fullReadThreshold = 131072; const oaUInt4 oaDatabase::s_fullWriteThreshold = 32768; const oaUInt4 oaDatabase::s_swapCheckOffset = 0; const oaUInt4 oaDatabase::s_idOffset = 4; const oaUInt4 oaDatabase::s_schemaRevNumOffset = 6; const oaUInt4 oaDatabase::s_databaseTocOffset = 8; const oaUInt2 oaDatabase::s_baseSchemaRev = 0;
However these formats are also acceptable:
// ***************************************************************************** // Initialize Static Members // ***************************************************************************** oaUInt4 oaDatabase::s_size = 16; const oaUInt4 oaDatabase::s_fullReadThreshold = 131072; const oaUInt4 oaDatabase::s_fullWriteThreshold = 32768; const oaUInt4 oaDatabase::s_swapCheckOffset = 0; const oaUInt4 oaDatabase::s_idOffset = 4; const oaUInt4 oaDatabase::s_schemaRevNumOffset = 6; const oaUInt4 oaDatabase::s_databaseTocOffset = 8; const oaUInt2 oaDatabase::s_baseSchemaRev = 0; // ***************************************************************************** // Initialize Static Members // ***************************************************************************** oaUInt4 oaDatabase::s_size = 16; const oaUInt4 oaDatabase::s_fullReadThreshold = 131072; const oaUInt4 oaDatabase::s_fullWriteThreshold = 32768; const oaUInt4 oaDatabase::s_swapCheckOffset = 0; const oaUInt4 oaDatabase::s_idOffset = 4; const oaUInt4 oaDatabase::s_schemaRevNumOffset = 6; const oaUInt4 oaDatabase::s_databaseTocOffset = 8; const oaUInt2 oaDatabase::s_baseSchemaRev = 0;
The OpenAccess coding standards require judicious use of const
as a way of providing read-only access to data. Properly used, const
lets you develop code that gives access to private data without copying
the data to protect it.
Everything that can be const
, should be const
, and
no more. Although this rule can be applied in a number of contexts, the main
focus is member functions, pointers, and references. Apply
const
consistently and correctly from the beginning, starting with
the most "primitive" functions (which includes all member functions), in the
following order:
const
.const
.
const
if all uses of them
are const
.const
if it really makes sense to restrict clients to read-only
access to the object being returned (this is one area where excessive use of
const
can be harmful).const
is also useful for documenting the programmer's intent with regards to data members and local variables. For example, local variables are often used simply to hold a copy of a value created by some complex expression or
other expensive operation and are not really "variables" at all. These local variables should be const
.
In the following example, it is immediately apparent that count
is not modified and its value is readily known wherever it occurs in the code.
const oaInt4 count = inst->countTerminalsConnectedToHighFanoutNets();
Use the mutable keyword to identify data members that need to be modified by const member functions. The traditional way to modify the private, internal state of an object within a const member function is to cast away the const-ness of the this pointer and then access (and modify) the member through the resultant non-const expression. C++ now provides a better, more explicit alternative to this sort of casting: the mutable keyword. Syntactically, mutable is like a storage class (think of static) and precedes any type specifier for a data member. A data member that is declared to be mutable can be modified even within a const member function. Just as with the casting technique, modification of mutable data should not result in any externally visible change to the state of an object, otherwise the const designation on the member function is not accurate. Always use the mutable keyword instead of the casting technique.
Following are examples of both correct and incorrect methods for identifying data members that can be modified by const member functions.
class SomeClass { public: ... oaInt4 getSomething() const; ... private: static oaInt4 computeSomething(); mutable oaInt4 m_somethingRight; oaInt4 m_somethingWrong; }; // This implementation is correct, because it properly makes // use of the mutable qualifier on somethingRight. oaInt4 SomeClass::getSomething() { if (m_somethingRight == 0) { m_somethingRight = computeSomething(); } return m_somethingRight; } // This implementation is incorrect, because it // improperly casts away the const-ness of this // in order to modify somethingWrong. oaInt4 SomeClass::getSomething() { if (m_somethingWrong == 0) { (const_cast<SomeClass *>(this))->m_somethingWrong = computeSomething(); } return m_somethingWrong; }
Copyright © 2002-2010 Cadence Design Systems, Inc.
All rights reserved.