Class, Structure, and Enum Declarations and Definitions


Cleanly formatted class, structure, and enum definitions greatly affect the readability of source code.

Common Coding Standard for Classes, Structures, and Enumerations

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
};


Member Names

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

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.



Classes or Structs?

For C++ development, classes are the preferable object for almost all development. However, a struct may be more appropriate if:



Static Class Members

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;



Using const

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:

  1. If a member function does not modify the object it is a member of, declare it const.
  2. If function parameters are pointers or references, and if the function makes only read-only use of them, declare them const.
  3. Declare local pointer variables const if all uses of them are const.
  4. Declare function return types and data members that are pointer types 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 Instead of Casting Away const-ness

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;
}





Return to top of page