Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C++ Coding Standards


October, 2004: C++ Coding Standards

Herb Sutter (http://www.gotw.ca/) chairs the ISO C++ Standards committee and is a Visual C++ architect at Microsoft, where he is responsible for designing C++ language extensions for .NET programming (C++/CLI). His two most recent books are Exceptional C++ Style and C++ Coding Standards.

Andrei Alexandrescu is a graduate student in Computer Science at the University of Washington and author of Modern C++ Design. He can be contacted at [email protected].

This article is based on their most recent book, C++ Coding Standards (Addison-Wesley Professional, 2005; ISBN 0321113586).


Get into a rut early: Do the same process the same way. Accumulate idioms. Standardize. The only difference(!) between Shakespeare and you was the size of his idiom list'not the size of his vocabulary.

—Alan Perlis [emphasis ours]

The best thing about standards is that there are so many to choose from.

—Variously attributed

The goal of our new book, C++ Coding Standards, is to identify and summarize tried-and-true guidelines in concise one- and two-page Items, with extensive references to further details in the C++ literature. Among the topics we examine are Organizational and Policy Issues, Design Style, Templates and Genericity, Coding Style, Functions and Operators; Class Design and Inheritance, Construction and Destruction, Namespaces and Modules, Error Handing and Exceptions, and more. For this month's column, I've selected the book's opening Item and two others that appear later in the book.

Don't sweat the small stuff. (Or: Know what not to standardize.)

Don't enforce matters of personal taste. Don't enforce obsolete practices.

Issues that are really just personal taste and don't affect correctness or readability don't belong in a coding standard. Any professional programmer can easily read and write code that is formatted a little differently than they're used to.

Do use consistent formatting within each source file or even each project, because it's jarring to jump around among several styles in the same piece of code. But don't try to enforce consistent formatting across multiple projects or across a company.

Here are several common issues where the important thing is not to set a rule but just to be consistent with the style already in use within the file you're maintaining:

  • Don't specify how much to indent, but do indent to show structure: Use any number of spaces you like to indent, but be consistent within at least each file.
  • Don't enforce a specific line length, but do keep line lengths readable: Use any length of line you like, but don't be excessive. Studies show that up to 10-word text widths are optimal for eye tracking.
  • Don't overlegislate naming, but do use a consistent naming convention: There are only two must-dos: a) never use "underhanded names," ones that begin with an underscore or that contain a double underscore; and b) always use ONLY_UPPERCASE_NAMES for macros and never think about writing a macro that is a common word or abbreviation (including common template parameters, such as T and U; writing #define T anything is extremely disruptive). Otherwise, do use consistent and meaningful names and follow a file's or module's convention. (If you can't decide on your own naming convention, try this one: Name classes, functions and enums LikeThis; name variables likeThis; name private member variables likeThis_; and name macros LIKE_THIS.)
  • Don't prescribe commenting styles (except where tools extract certain styles into documentation), but do write useful comments: Write code instead of comments where possible (e.g., see Item 16). Don't write comments that repeat the code; they get out of sync. Do write illuminating comments that explain approach and rationale.

Finally, don't try to enforce antiquated rules (see Examples 3 and 4) even if they once appeared in older coding standards.

Examples

Example 1: Brace placement. There is no readability difference among:

void using_k_and_r_style() {
   // ...
}
void putting_each_brace_on_its_own_line()
{
   // ...
}
void or_with_the_braces_indented()
   {
   // ...
   }

Any professional programmer can easily read and write any of these styles without hardship. But do be consistent: Don't just place braces randomly or in a way that obscures scope nesting, and try to follow the style already in use in each file. In this book, our brace placement choices are motivated by maximizing readability within our editorial constraints.

Example 2: Spaces vs. tabs. Some teams legitimately choose to ban tabs (e.g., [BoostLRG]), on the grounds that tabs vary from editor to editor and, when misused, turn indenting into outdenting and nondenting. Other equally respectable teams legitimately allow tabs, adopting disciplines to avoid their potential drawbacks. Just be consistent: If you do allow tabs, ensure it is never at the cost of code clarity and readability as team members maintain each other's code (see Item 6). If you don't allow tabs, allow editors to convert spaces to tabs when reading in a source file so that the user can work with tabs while in the editor, but ensure they convert the tabs back to spaces when writing the file back out.

Example 3: Hungarian notation. Notations that incorporate type information in variable names have mixed utility in type-unsafe languages (notably C), are possible but have no benefits (only drawbacks) in object-oriented languages, and are impossible in generic programming. Therefore no C++ coding standard should require Hungarian notation, though a C++ coding standard might legitimately choose to ban it.

Example 4: Single entry, single exit ("SESE"). Historically, some coding standards have required that each function have exactly one exit, meaning one return statement. Such a requirement is obsolete in languages that support exceptions and destructors, where functions typically have numerous implicit exits. Instead, follow standards like Item 5 that directly promote simpler and shorter functions that are inherently easier to understand and to make error-safe.

References

[BoostLRG], [Brooks95] 12, [Constantine95] 29, [Keffer95] p. 1, [Kernighan99] 1.1, 1.3, 1.6-7, [Lakos96] pp. 34-38, 91-93, [McConnell93] 9, 19, [Stroustrup94] 4.2-3, [Stroustrup00] 4.9.3, 6.4, 7.8, C.1, [Sutter00] 6, 20, [SuttHysl01].

Use explicit RAII and smart pointers. Ensure resources are owned by objects.

C++'s "resource acquisition is initialization" (RAII) idiom is a powerful tool for correct resource handling. RAII allows the compiler to provide strong and automated guarantees that in other languages require fragile hand-coded idioms.

When allocating a raw resource, always immediately pass it to an owning object. Never allocate more than one resource in a single statement.

C++'s language-enforced constructor/destructor symmetry mirrors the symmetry inherent in resource acquire/release function pairs such as fopen/fclose, lock/unlock, and new/delete. This makes a stack-based (or reference-counted) object with a resource-acquiring constructor and a resource-releasing destructor an excellent tool for automating resource management and cleanup.

The automation is easy to implement, elegant, low-cost, and inherently error-safe. If you choose not to use it, you are choosing the nontrivial and attention-intensive task of pairing the calls correctly by hand, including in the presence of branched control flows and exceptions. Such C-style reliance on micromanaging resource deallocation is unacceptable when C++ provides direct automation via easy-to-use RAII.

Whenever you deal with a resource that needs paired acquire/release function calls, encapsulate that resource in an object that enforces pairing for you and performs the resource release in its destructor. For example, instead of calling a pair of OpenPort/ClosePort nonmember functions directly, consider:

class Port {
public:
  Port( const string& destination ); // call OpenPort
  ~Port();		         // call ClosePort
  // ... ports can't usually be cloned, so disable 
  // copying and assignment ...
};
void DoSomething() {
  Port port1( "server1:80" );
  // ...
} // can't forget to close port1; it's closed 
  //          automatically at the end of the scope
shared_ptr<Port> port2; // port2 is closed automatically when                          
                      // the last shared_ptr referring to 
                      // it goes away

You can also use libraries that implement the pattern for you (see [Alexandrescu00c]).

When implementing RAII, beware of copy construction and assignment (see Item 48). If you choose to support them, implement the copy constructor to duplicate the resource or track the number of uses with a technique such as reference counting (the compiler-generated copy constructor almost certainly won't be correct), and have the assignment operator do the same and ensure that it frees its originally held resource if necessary. One classic oversight is to free the old resource before making sure the new resource is properly duplicated and ready for use (see Item 71). Otherwise, explicitly disable them by making them private and undefined (see Item 56).

Make sure that all resources are owned by objects. To this end, perform every explicit resource allocation (e.g., new) in its own statement which immediately gives the allocated resource to a manager object (e.g., shared_ptr); prefer smart pointers to dumb pointers. Otherwise you can leak resources because the order of evaluation of a function's parameters is undefined. (See Item 31.) For example, consider a function taking as its parameters two smart pointers to new-allocated Widget objects:

void Fun( shared_ptr<Widget> sp1, 
         shared_ptr<Widget> sp2 );
// ...
Fun( shared_ptr<Widget>(new Widget), 
     shared_ptr<Widget>(new Widget) );

Such code is unsafe. The C++ standard gives compilers great leeway to reorder the two expressions building the function's two arguments. In particular, the compiler can interleave execution of the two expressions: Memory allocation (by calling operator new) could be done first for both objects, followed by attempts to call the two Widget constructors. That very nicely sets things up for a leak because if one of the constructor calls throws an exception then the other object's memory will never be released! (See [Sutter02] for details.)

This very subtle problem nonetheless has a very simple solution: Follow the advice to never allocate more than one resource in a single statement, and perform every explicit resource allocation (e.g., new) in its own code statement which immediately gives the resource to an owning object (e.g., shared_ptr). For example:

shared_ptr sp1(new Widget), 
          sp2(new Widget);
Fun( sp1, sp2 );

See also Item 31 for other advantages to using this style.

References

[Alexandrescu00c], [Cline99] 31.03-05, [Dewhurst03] 24, 67, [Meyers96] 9-10, [Stroustrup00] 14.3-4, 25.7, E.3, E.6, [Sutter00] 16, [Sutter02] 20-21, [Vandevoorde03] 20.1.4.

Blend static and dynamic polymorphism judiciously.

Static and dynamic polymorphism are complementary. Understand their tradeoffs, use each for what it's best at, and mix them to get the best of both worlds.

Dynamic polymorphism comes in the form of classes with virtual functions and instances manipulated indirectly (through pointers or references). Static polymorphism involves template classes and template functions.

Polymorphism means two things:

  • A given value can have more than one type.
  • A given function can accept arguments of types other than the exact types of its formal parameters.

The strength of polymorphism is that the same piece of code can operate on different types, even types that were not known at the time the code was written. Such "post-hoc applicability" is the cornerstone of polymorphism because it amplifies the usefulness and reusability of code (see Item 38). (Contrast that with monomorphic code that rigidly operates only on the concrete types it was meant to work with.)

Dynamic polymorphism via public inheritance lets a value have more than one type. For example, a Derived* p can be viewed as a pointer to not only a Derived, but to an object of any type Base that's a direct or indirect base of Derived (the subsumption property). Dynamic polymorphism is also referred to as inclusion polymorphism because the set modeled by Base includes the specializations modeled by Derived.

Due to its characteristics, dynamic polymorphism in C++ is best at:

  • Uniform manipulation based on superset/subset relationships: Different classes that hold a superset/subset (base/derived) relationship can be treated uniformly. A function that works on Employee objects works also on Secretary objects.
  • Static type checking: All types are checked statically in C++.
  • Dynamic binding and separate compilation: Code that uses classes in a hierarchy can be compiled apart from the code of the entire hierarchy. This is possible because of the indirection that pointers provide (both to objects and to functions).
  • Binary interfacing: Modules can be linked either statically or dynamically, as long as the linked modules lay out the virtual tables the same way.

Static polymorphism via templates also lets a value have more than one type. Inside a template<class T> void f( T t ) { /*...*/ }, t can have any type that can be substituted inside f to render compilable code. This is called an "implicit interface," in contrast to a base class's explicit interface. It achieves the same goal of polymorphism—writing code that operates on multiple types—but in a very different way.

Static polymorphism is best at:

  • Uniform manipulation based on syntactic and semantic interface: Types that obey a syntactic and semantic interface can be treated uniformly. Interfaces are syntactic and implicit (not signature-based and explicit), and so allow any type substitution that fits a given syntax. For example, given the statement int i = p->f(5): If p is a pointer to a Base class type, this calls a specific interface function, such as perhaps a virtual int f(int). But if p is of a generic type, this call can bind to a myriad of things, including that it might invoke an overloaded operator-> that returns a type defining the function X f(double) where X is convertible to int.
  • Static type checking: All types are checked statically.
  • Static binding (prevents separate compilation): All types are bound statically.
  • Efficiency: Compile-time evaluation and static binding allow optimizations and efficiencies not available with dynamic binding.

Decide on your priorities, and use each type of polymorphism for its strengths.

Prefer to blend both kinds of polymorphism to combine their benefits while trying not to combine their drawbacks:

  • Static helps dynamic: Use static polymorphism to implement dynamically polymorphic interfaces. For example, you might have an abstract base class Command and define various implementations as template</*...*/> class ConcreteCommand : public Command. Examples include implementing the Command and Visitor design patterns (see [Alexandrescu01] and [Sutter04]).
  • Dynamic helps static: Offer a generic, comfortable, statically-bound interface, but dispatch dynamically internally so you offer a uniform object layout. Good examples are discriminated union implementations (see [Alexandrescu02] and [Boost]) and tr1::shared_ptr's Deleter parameter (see [C++TR104]).
  • Any other blend: Just remember that a bad blend that combines the weaknesses of both is worse than using only one in isolation, while a good blend that combines the benefits of both is better than either alone. For example, don't put virtual functions into a templated type unless you intend all virtual functions to be instantiated with any use of the type (this is in sharp contrast to non-virtual functions of templated types). The code size hit can be astronomical, and you may overconstrain your generic type by instantiating functionality that is never needed. We made this mistake in the standard facets. Don't make it again.

References

[Alexandrescu01] 10, [Alexandrescu02], [C++TR104], [Gamma95], [Stroustrup00] 24.4.1, [Sutter00] 3, [Sutter02] 1, [Sutter04] 17, 35, [Vandevoorde03] 14.

Bibliography

See http://www.gotw.ca/publications/c++cs/bibliography.htm for the full bibliography, including the references mentioned here.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.