C++ Coding Standards

Herb and Andrei present tried-and-true C++ Coding Standards from their new book of the same name.


October 01, 2004
URL:http://www.drdobbs.com/c-coding-standards/184401863

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:

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:

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:

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:

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:

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.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.