Header File Dependencies
How can we use a class Foo
in a header file without access to its definition?
- We can declare data members of type
Foo*
orFoo&
. - We can declare (but not define) functions with arguments, and/or return values, of type
Foo
. - We can declare static data members of type
Foo
. This is because static data members are defined outside the class definition.
On the other hand, you must include the header file for Foo
if your class subclasses Foo
or has a data member of type Foo
.
Inline Functions
A decent rule of thumb is to not inline a function if it is more than 10 lines long. Beware of destructors, which are often longer than they appear because of implicit member- and base-destructor calls!
Another useful rule of thumb: it's typically not cost effective to inline functions with loops or switch statements (unless, in the common case, the loop or switch statement is never executed).
Function Parameter Ordering
When defining a function, parameter order is: inputs, then output
Parameters to C/C++ functions are either input to the function, output from the function, or both. Input parameters are usually values or const
references, while output and input/output parameters will be non-const
pointers. When ordering function parameters, put all input-only parameters before any output parameters. In particular, do not add new parameters to the end of the function just because they are new; place new input-only parameters before the output parameters.
Local Variables
There is one caveat: if the variable is an object, its constructor is invoked every time it enters scope and is created, and its destructor is invoked every time it goes out of scope.
// Inefficient implementation:
for (int i = 0; i < 1000000; ++i) {
Foo f; // My ctor and dtor get called 1000000 times each.
f.DoSomething(i);
}
It may be more efficient to declare such a variable used in a loop outside that loop:
Foo f; // My ctor and dtor get called once each.
for (int i = 0; i < 1000000; ++i) {
f.DoSomething(i);
}
Doing Work in Constructors
Do only trivial initialization in a constructor. If at all possible, use an Init()
method for non-trivial initialization.
Decision: If your object requires non-trivial initialization, consider having an explicit Init()
method and/or adding a member flag that indicates whether the object was successfully initialized.
Explicit Constructors
Use the C++ keyword explicit
for constructors with one argument.
Definition: Normally, if a constructor takes one argument, it can be used as a conversion. For instance, if you define Foo::Foo(string name)
and then pass a string to a function that expects a Foo
, the constructor will be called to convert the string into a Foo
and will pass the Foo
to your function for you. This can be convenient but is also a source of trouble when things get converted and new objects created without you meaning them to. Declaring a constructor explicit
prevents it from being invoked implicitly as a conversion.
Pros: Avoids undesirable conversions.
Cons: None.
Decision:
We require all single argument constructors to be explicit. Always put explicit
in front of one-argument constructors in the class definition: explicit Foo(string name);
The exception is copy constructors, which, in the rare cases when we allow them, should probably not be explicit
. Classes that are intended to be transparent wrappers around other classes are also exceptions. Such exceptions should be clearly marked with comments.
Copy Constructors
Consider adding dummy declarations for the copy constructor and assignment operator in the class' private:
section, without providing definitions. With these dummy routines marked private, a compilation error will be raised if other code attempts to use them. For convenience, a DISALLOW_COPY_AND_ASSIGN
macro can be used:
// A macro to disallow the copy constructor and operator= functions
// This should be used in the private: declarations for a class
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&); \
void operator=(const TypeName&)Then, in class Foo:
class Foo {
public:
Foo(int f);
~Foo();
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
};
Structs vs. Classes
Use a struct
only for passive objects that carry data; everything else is a class
.
Declaration Order
Your class definition should start with its public:
section, followed by its protected:
section and then its private:
section. If any of these sections are empty, omit them.
Within each section, the declarations generally should be in the following order:
- Typedefs and Enums
- Constants
- Constructors
- Destructor
- Methods, including static methods
- Data Members, including static data members
The DISALLOW_COPY_AND_ASSIGN
macro invocation should be at the end of the private:
section. It should be the last thing in the class.
Reference Arguments
All parameters passed by reference must be labeled const
.
Within function parameter lists all references must be const
:
void Foo(const string &in, string *out);
In fact it is a very strong convention that input arguments are values or const
references while output arguments are pointers. Input parameters may be const
pointers, but we never allow non-const
reference parameters.
Casting
Use C++ casts like static_cast<>()
. Do not use other cast formats like int y = (int)x;
or int y = int(x);
.
Do not use C-style casts. Instead, use these C++-style casts.
- Use
static_cast
as the equivalent of a C-style cast that does value conversion, or when you need to explicitly up-cast a pointer from a class to its superclass. - Use
const_cast
to remove theconst
qualifier (see const). - Use
reinterpret_cast
to do unsafe conversions of pointer types to and from integer and other pointer types. Use this only if you know what you are doing and you understand the aliasing issues. - Do not use
dynamic_cast
except in test code. If you need to know type information at runtime in this way outside of a unittest, you probably have a design flaw.
sizeof
Use sizeof(varname)
instead of sizeof(type)
whenever possible.
Use sizeof(varname)
because it will update appropriately if the type of the variable changes. sizeof(type)
may make sense in some cases, but should generally be avoided because it can fall out of sync if the variable's type changes.
Struct data;
memset(&data, 0, sizeof(data));
memset(&data, 0, sizeof(Struct));
File Comments
Every file should contain the following items, in order:
- a copyright statement (for example,
Copyright 2008 Google Inc.
) - a license boilerplate. Choose the appropriate boilerplate for the license used by the project (for example, Apache 2.0, BSD, LGPL, GPL)
- an author line to identify the original author of the file
File Contents
Every file should have a comment at the top, below the and author line, that describes the contents of the file.
Generally a .h
file will describe the classes that are declared in the file with an overview of what they are for and how they are used. A .cc
file should contain more information about implementation details or discussions of tricky algorithms. If you feel the implementation details or a discussion of the algorithms would be useful for someone reading the .h
, feel free to put it there instead, but mention in the .cc
that the documentation is in the .h
file.
Do not duplicate comments in both the .h
and the .cc
. Duplicated comments diverge.
Every class definition should have an accompanying comment that describes what it is for and how it should be used.
// Iterates over the contents of a GargantuanTable. Sample usage:
// GargantuanTable_Iterator* iter = table->NewIterator();
// for (iter->Seek("foo"); !iter->done(); iter->Next()) {
// process(iter->key(), iter->value());
// }
// delete iter;
class GargantuanTable_Iterator {
...
};
Function Comments
Declaration comments describe use of the function; comments at the definition of a function describe operation.
Function Declarations
Every function declaration should have comments immediately preceding it that describe what the function does and how to use it. These comments should be descriptive ("Opens the file") rather than imperative ("Open the file"); the comment describes the function, it does not tell the function what to do. In general, these comments do not describe how the function performs its task. Instead, that should be left to comments in the function definition.
Types of things to mention in comments at the function declaration:
- What the inputs and outputs are.
- For class member functions: whether the object remembers reference arguments beyond the duration of the method call, and whether it will free them or not.
- If the function allocates memory that the caller must free.
- Whether any of the arguments can be
NULL
. - If there are any performance implications of how a function is used.
- If the function is re-entrant. What are its synchronization assumptions?
Here is an example:
// Returns an iterator for this table. It is the client's
// responsibility to delete the iterator when it is done with it,
// and it must not use the iterator once the GargantuanTable object
// on which the iterator was created has been deleted.
//
// The iterator is initially positioned at the beginning of the table.
//
// This method is equivalent to:
// Iterator* iter = table->NewIterator();
// iter->Seek("");
// return iter;
// If you are going to immediately seek to another place in the
// returned iterator, it will be faster to use NewIterator()
// and avoid the extra seek.
Iterator* GetIterator() const;