C++ Coding Standards: Classes And Methods - Online Article

Classes

Naming Class Files

Class Definition in One File

Each class definition should be in its own file where each file is named directly after the class's name:

   ClassName.h

Implementation in One File

In general each class should be implemented in one source file:

   ClassName.cc   // or whatever the extension is: cpp, c++

But When it Gets Really Big...

If the source file gets too large or you want to avoid compiling templates all the time then add additional files named according to the following rule:

   ClassName_section.C

section is some name that identifies why the code is chunked together. The class name and section name are separated by '_'.

Class Layout

A common class layout is critical from a code comprehension point of view and for automatically generating documentation. C++ programmers, through a new set of tools, can enjoy the same level generated documentation Java programmers take for granted.

Class and Method Documentation

It is recommended a program like Doxygen be used to document C++ classes, method, variables, functions, and macros. The documentation can be extracted and put in places in a common area for all programmers to access. This saves programmers having to read through class headers. Documentation generation should be integrated with the build system where possible.

Template

Please use the following template when creating a new class.

/**  A one line description of the class.  
*
* #include "XX.h" <BR>
* -llib
*
* A longer description.
*
* @see something
*/

#ifndef XX_h
#define XX_h

// SYSTEM INCLUDES
//

// PROJECT INCLUDES
//

// LOCAL INCLUDES
//

// FORWARD REFERENCES
//


class XX
{
public:
// LIFECYCLE

/** Default constructor.
*/
XX(void);


/** Copy constructor.
*
* @param from The value to copy to this object.
*/
XX(const XX& from);


/** Destructor.
*/
~XX(void);


// OPERATORS

/** Assignment operator.
*
* @param from THe value to assign to this object.
*
* @return A reference to this object.
*/
XX& operator=(const XX& from);

// OPERATIONS
// ACCESS
// INQUIRY

protected:
private:
};

// INLINE METHODS
//

// EXTERNAL REFERENCES
//

#endif // _XX_h_

Required Methods Placeholders

The template has placeholders for required methods. You can delete them or implement them.

Ordering is: public, protected, private

Notice that the public interface is placed first in the class, protected next, and private last. The reasons are:

  • programmers should care about a class's interface more than implementation
  • when programmers need to use a class they need the interface not the implementation

It makes sense then to have the interface first. Placing implementation, the private section, first is a historical accident as the first examples used the private first layout. Over time emphasis has switched deemphasizing a class's interface over implementation details.

LIFECYCLE

The life cycle section is for methods that control the life cycle of an object. Typically these methods include constructors, destructors, and state machine methods.

OPERATORS

Place all operators in this section.

OPERATIONS

Place the bulk of a class's non access and inquiry method methods here. A programmer will look here for the meat of a class's interface.

ACCESS

Place attribute accessors here.

INQUIRY

These are the Is* methods. Whenever you have a question to ask about an object it can be asked via in Is method. For example: IsOpen() will indicate if the object is open. A good strategy is instead of making a lot of access methods you can turn them around to be questions about the object thus reducing the exposure of internal structure. Without the IsOpen() method we might have had to do: if (STATE_OPEN == State()) which is much uglier.

What should go in public/protected/private?

Public Section

Only put an object's interface in the public section. DO NOT expose any private data items in the public section. At least encapsulate access via access methods. Ideally your method interface should make most access methods unnecessary. Do not put data in the public interface.

Protected and Private Section

What should go into the protected section versus the private section is always a matter of debate.

All Protected

Some say there should be no private section and everything not in the public section should go in the protected section. After all, we should allow all our children to change anything they wish.

All Private

Another camp says by making the public interface virtual any derived class can change behavior without mucking with internals.

Wishy Washy

Rationally decide where elements should go and put them there. Not very helpful.

And the Winner Is...

Keeping everything all private seems the easiest approach. By making the public methods virtual flexibility is preserved.

Prototype Source File

#include "XX.h"  // class implemented


/////////////////////////////// PUBLIC ///////////////////////////////////////

//============================= LIFECYCLE ====================================

XX::XX()
{
}// XX

XX::XX(const XX&)
{
}// XX

XX::~XX()
{
}// ~XX


//============================= OPERATORS ====================================

XX&
XX::operator=(const XX&);
{
return *this;

}// =

//============================= OPERATIONS ===================================
//============================= ACESS ===================================
//============================= INQUIRY ===================================
/////////////////////////////// PROTECTED ///////////////////////////////////

/////////////////////////////// PRIVATE ///////////////////////////////////

Use Header File Guards

Include files should protect against multiple inclusion through the use of macros that "guard" the files.

When Not Using Namespces

#ifndef filename_h
#define filename_h

#endif

The new line after the endif if is required by some compilers.

When Using Namespaces

If namespaces are used then to be completely safe:

#ifndef namespace_filename_h
#define namespace_filename_h

#endif
  1. Replace filename with the name of the file being guarded. This should usually be the name of class contained in the file. Use the exact class name. Some standards say use all upper case. This is a mistake because someone could actually name a class the same as yours but using all upper letters. If the files end up be included together one file will prevent the other from being included and you will be one very confused puppy. It has happened!
  2. Most standards put a leading _ and trailing _. This is no longer valid as the C++ standard reserves leading _ to compiler writers.
  3. When the include file is not for a class then the file name should be used as the guard name.
  4. Compilers differ on how comments are handled on preprocessor directives. Historically many compilers have not accepted comments on preprocessor directives.
  5. Historically many compilers require a new line after last endif.

Required Methods for a Class

To be good citizens almost all classes should implement the following methods. If you don't have to define and implement any of the "required" methods they should still be represented in your class definition as comments.

  • Default Constructor

    If your class needs a constructor, make sure to provide one. You need one if during the operation of the class it creates something or does something that needs to be undone when the object dies. This includes creating memory, opening file descriptors, opening transactions etc.

    If the default constructor is sufficient add a comment indicating that the compiler-generated version will be used.

    If your default constructor has one or more optional arguments, add a comment indicating that it still functions as the default constructor.

  • Virtual Destructor

    If your class is intended to be derived from by other classes then make the destructor virtual.

  • Copy Constructor

    If your class is copyable, either define a copy constructor and assignment operator or add a comment indicating that the compiler-generated versions will be used.

    If your class objects should not be copied, make the copy constructor and assignment operator private and don't define bodies for them. If you don't know whether the class objects should be copyable, then assume not unless and until the copy operations are needed.

  • Assignment Operator

    If your class is assignable, either define a assignment operator or add a comment indicating that the compiler-generated versions will be used.

    If your objects should not be assigned, make the assignment operator private and don't define bodies for them. If you don't know whether the class objects should be assignable, then assume not.

Justification

  • Virtual destructors ensure objects will be completely destructed regardless of inheritance depth. You don't have to use a virtual destructor when:
    • You don't expect a class to have descendants.
    • The overhead of virtualness would be too much.
    • An object must have a certain data layout and size.
  • A default constructor allows an object to be used in an array.
  • The copy constructor and assignment operator ensure an object is always properly constructed.

The Law of The Big Three

A class with any of (destructor, assignment operator, copy constructor) generally needs all 3. For more information see http://www.parashift.com/c++-faq-lite/coding-standards.html#[25.9].

Example

The default class template with all required methods. An example using default values:

class Planet
{
public:
/** The following is the default constructor if no arguments are supplied.
*/
Planet(int radius= 5);

// Use compiler-generated copy constructor, assignment, and destructor.
// Planet(const Planet&);
// Planet& operator=(const Planet&);
// ~Planet();
};

Method Layout

The approach used is to place a comment block before each method that can be extracted by a tool and be made part of the class documentation. Here we'll use Doxygen which supports the Javadoc format. See the Doxygen documentation for a list of attributes supported by the document generator.

Method Header

Every parameter should be documented. Every return code should be documented. All exceptions should be documented. Use complete sentences when describing attributes. Make sure to think about what other resources developers may need and encode them in with the @see attributes.

  /** Assignment operator.
*
*
* @param val The value to assign to this object.
* @exception LibaryException The explanation for the exception.
* @return A reference to this object.
*/
XX& operator=(XX& val);

Additional Sections

In addition to the standard attribute set, the following sections can be included in the documentation:

  1. PRECONDITION
    Document what must have happened for the object to be in a state where the method can be called.
  2. WARNING
    Document anything unusual users should know about this method.
  3. LOCK REQUIRED
    Some methods require a semaphore be acquired before using the method. When this is the case use lock required and specify the name of the lock.
  4. EXAMPLES
    Include exampes of how to use a method. A picture says a 1000 words, a good example answers a 1000 questions.

For example:

  /** Copy one string to another.
*
* PRECONDITION

* REQUIRE(from != 0)
* REQUIRE(to != 0)
*
* WARNING

* The to buffer must be long enough to hold
* the entire from buffer.
*
* EXAMPLES

*
   * strcpy(somebuf, "test")
*

*
* @param from The string to copy.
* @param to The buffer to copy the string to.
*
* @return void
*/
void strcpy(const char* from, char* to);

Common Exception Sections

If the same exceptions are being used in a number of methods, then the exceptions can be documented once in the class header and referred to from the method documentation.

Formatting Methods with Multiple Arguments

We should try and make methods have as few parameters as possible. If you find yourself passing the same variables to every method then that variable should probably be part of the class. When a method does have a lot of parameters format it like this:

   int                     AnyMethod(
int arg1,
int arg2,
int arg3,
int arg4);

Different Accessor Styles

Why Accessors?

Access methods provide access to the physical or logical attributes of an object. Accessing an object's attributes directly as we do for C structures is greatly discouraged in C++. We disallow direct access to attributes to break dependencies, the reason we do most things. Directly accessing an attribute exposes implementation details about the object.

To see why ask yourself:

  • What if the object decided to provide the attribute in a way other than physical containment?
  • What if it had to do a database lookup for the attribute?
  • What if a different object now contained the attribute?

If any of the above changed code would break. An object makes a contract with the user to provide access to a particular attribute; it should not promise how it gets those attributes. Accessing a physical attribute makes such a promise.

Accessors Considered Somewhat Harmful

At least in the public interface having accessors many times is an admission of failure, a failure to make an object's interface complete. At the protected or private level accessors are fine as these are the implementation levels of a class.

Implementing Accessors

There are three major idioms for creating accessors.

Get/Set

   class X
{
public:
int GetAge() const { return mAge; }
void SetAge(int age) { mAge= age; }
private:
int mAge;
}

The problem with Get/Set is twofold:

  • It's ugly. Get and Set are strewn throughout the code cluttering it up.
  • It doesn't treat attributes as objects in their own right. An object will have an assignment operator. Why shouldn't age be an object and have its own assignment operator ?

One benefit, that it shares with the One Method Name, is when used with messages the set method can transparently transform from native machine representations to network byte order.

One Method Name

   class X
{
public:
int Age() const { return mAge; }
void Age(int age) { mAge= age; }
private:
int mAge;
}

Similar to Get/Set but cleaner. Use this approach when not using the Attributes as Objects approach.

Attributes as Objects

   class X
{
public:
int Age() const { return mAge; }
int& rAge() { return mAge; }

const String& Name() const { return mName; }
String& rName() { return mName; }
private:
int mAge;
String mName;
}

The above two attribute examples shows the strength and weakness of the Attributes as Objects approach.

When using an int type, which is not a real object, the int is set directly because rAge() returns a reference. The object can do no checking of the value or do any representation reformatting. For many simple attributes, however, these are not horrible restrictions. A way around this problem is to use a class wrapper around base types like int.

When an object is returned as reference its = operator is invoked to complete the assignment. For example:

   X x;
x.rName()= "test";

This approach is also more consistent with the object philosophy: the object should do it. An object's = operator can do all the checks for the assignment and it's done once in one place, in the object, where it belongs. It's also clean from a name perspective.

When possible use this approach to attribute access.

Init Idiom for Initializing Objects

  • Objects with multiple constructors and/or multiple attributes should define a private Init() method to initialize all attributes. If the number of different member variables is small then this idiom may not be a big win and C++'s constructor initialization syntax can/should be used.

Justification

  • When using C++'s ability to initialize variables in the constructor it's difficult with multiple constructors and/or multiple attributes to make sure all attributes are initialized. When an attribute is added or changed almost invariably we'll miss changing a constructor.
  • It's better to define one method, Init(), that initializes all possible attributes. Init() should be called first from every constructor.

  • The Init() idiom cannot be used in two cases where initialization from a constructor is required:
    • constructing a member object
    • initializing a member attribute that is a reference

Example

   class Test
{
public:
Test()
{
Init(); // Call to common object initializer
}

Test(int val)
{
Init(); // Call to common object initializer
mVal= val;
}

private:
int mVal;
String* mpName;

void Init()
{
mVal = 0;
mpName= 0;
}
}

Since the number of member variables is small, this might be better
written as:

class Test
{
public:
Test(int val = 0, String* name = 0)
: mVal(val), mpName(name) {}
private:
int mVal;
String* mpName;
};

Initialize all Variables

  • You shall always initialize variables. Always. Every time.

Justification

  • More problems than you can believe are eventually traced back to a pointer or variable left uninitialized. C++ tends to encourage this by spreading initialization to each constructor. See Init Idiom for Initializing Objects.

Minimize Inlines

Minimize inlining in declarations or inlining in general. As soon as you put your C++ code in a shared library which you want to maintain compatibility with in the future, inlined code is a major pain in the butt. It's not worth it, for most cases.

Think About What Work to do in Constructors

Should you do work that can fail in constructors? If you have a compiler that does not support exceptions (or thread safe exceptions if it matters to you) then the answer is definitely no. Go directly to Do Work in Open.

If your compiler supports exception then go to Do Work in Constructor. There are still reasons to use an Open method even with exceptions.

Use Open Reasons

  1. It is difficult to write exception safe code in constructor. It's possible to throw an exception and not destruct objects allocated in the constructor. Use of auto_ptr can help prevent this problem.
  2. Some compilers do not support thread safe exceptions on all platforms.
  3. Virtual methods are not available in base classes. If the base class is expecting a virtual method implemented by derived classes to be available during construction then initialization must follow construction. This is common in frameworks.
  4. Larger scale state machines may dictate when initialization should occur. An object may contain numerous other objects that may have complex initialization conditions. In this case we could wait to construct objects but then we always have to worry about null pointers.
  5. If deletion is needed to free resources we still may want to keep the state around for debugging or statistics or as a supplier of information for other objects.

Do Work in Constructor

With exceptions work done in the constructor can signal failure so it is fine to perform real work in the constructor. This is the guru endorced approach as a matter of fact. But there are reasons to still use an open style approach.

The constructor code must still be very careful not to leak resources in the constructor. It's possible to throw an exception and not destruct objects allocated in the constructor.

There is a pattern called Resource Acquisition as Initialization that says all initialization is performed in the constructor and released in the destructor. The idea is that this is a safer approach because it should reduce resource leaks.

Do Work in Open

Do not do any real work in an object's constructor. Inside a constructor initialize variables only and/or do only actions that can't fail.

Create an Open() method for an object which completes construction. Open() should be called after object instantiation.

Example

   class Device
{
public:
Device() { /* initialize and other stuff */ }
int Open() { return FAIL; }
};

Device dev;
if (FAIL == dev.Open()) exit(1);

Don't Over Use Operators

C++ allows the overloading of all kinds of weird operators. Unless you are building a class directly related to math there are very few operators you should override. Only override an operator when the semantics will be clear to users.

Justification

  • Very few people will have the same intuition as you about what a particular operator will do.

Thin vs. Thick Class Interfaces

How many methods should an object have? The right answer of course is just the right amount, we'll call this the Goldilocks level. But what is the Goldilocks level? It doesn't exist. You need to make the right judgment for your situation, which is really what programmers are for :-)

The two extremes are thin classes versus thick classes. Thin classes are minimalist classes. Thin classes have as few methods as possible.

The expectation is users will derive their own class from the thin class adding any needed methods.

While thin classes may seem "clean" they really aren't. You can't do much with a thin class. Its main purpose is setting up a type. Since thin classes have so little functionality many programmers in a project will create derived classes with everyone adding basically the same methods. This leads to code duplication and maintenance problems which is part of the reason we use objects in the first place. The obvious solution is to push methods up to the base class. Push enough methods up to the base class and you getthick classes.

Thick classes have a lot of methods. If you can think of it a thick class will have it. Why is this a problem? It may not be. If the methods are directly related to the class then there's no real problem with the class containing them. The problem is people get lazy and start adding methods to a class that are related to the class in some willow wispy way, but would be better factored out into another class. Judgment comes into play again.

Thick classes have other problems. As classes get larger they may become harder to understand. They also become harder to debug as interactions become less predictable. And when a method is changed that you don't use or care about your code will still have to be recompiled, possibly retested, and rereleased.

Short Methods

  • Methods should limit themselves to a single page of code.

Justification

  • The idea is that the each method represents a technique for achieving a single objective.
  • Most arguments of inefficiency turn out to be false in the long run.
  • True function calls are slower than not, but there needs to a thought out decision (see premature optimization).

In a Source file Indicate if a Method is Static or Virtual

In a source file you can't tell a method is static or virtual because this information is in the header file. Knowing this information in a source file is useful and can be communicated using comments:

/*virtual*/ void
Class::method()
{
}

/*static*/ void
Class::method()
{
}

I've only seen this format once in source code, but it is interesting enough that I thought I would include here for your consideration. Notice how the method name sits alone on its own line. This looks more like C code and looks very clean for some reason.

About the Author:

No further information.




Comments

No comment yet. Be the first to post a comment.