The Artima Developer Community
The C++ Source | C++ Community News | Discuss | Print | Email | Screen Friendly Version | Previous | Next
Sponsored Link

The C++ Source
C++ Coding Standards
by Herb Sutter and Andrei Alexandrescu
November 19, 2004

Summary
The C++ Source is pleased to present an excerpt from Herb and Andrei's new book, C++ Coding Standards: 101 Rules, Guidelines, and Best Practices (Addison-Wesley, 2005). More than just style guidelines and "gotcha" warnings, this book clarifies the idioms and practices that pertain specifically to successful C++ software. Even better, you can't avoid deepening your mastery of the finer points of C++ as you read. This is the singularly authoritative Writ of Common Wisdom for the entire C++ development experience.

1. Compile cleanly at high warning levels.

Summary

Take warnings to heart: Use your compiler's highest warning level. Require clean (warning-free) builds. Understand all warnings. Eliminate warnings by changing your code, not by reducing the warning level.

Discussion

Your compiler is your friend. If it issues a warning for a certain construct, often there's a potential problem in your code.

Successful builds should be silent (warning-free). If they aren't, you'll quickly get into the habit of skimming the output, and you will miss real problems. (See Item 2.)

To get rid of a warning: a) understand it; and then b) rephrase your code to eliminate the warning and make it clearer to both humans and compilers that the code does what you intended.

Do this even when the program seemed to run correctly in the first place. Do this even when you are positive that the warning is benign. Even benign warnings can obscure later warnings pointing to real dangers.

Examples

Example 1: A third-party header file. A library header file that you cannot change could contain a construct that causes (probably benign) warnings. Then wrap the file with your own version that #includes the original header and selectively turns off the noisy warnings for that scope only, and then #include your wrapper throughout the rest of your project. Example (note that the warning control syntax will vary from compiler to compiler):

// File: myproj/my_lambda.h -- wraps Boost's lambda.hpp
//   Always include this file; don't use lambda.hpp directly.
//   NOTE: Our build now automatically checks "grep lambda.hpp <srcfile>".
// Boost.Lambda produces noisy compiler warnings that we know are innocuous.
// When they fix it we'll remove the pragmas below, but this header will still exist.
//
#pragma warning(push)	// disable for this header only
  #pragma warning(disable:4512)
  #pragma warning(disable:4180)
  #include <boost/lambda/lambda.hpp>
#pragma warning(pop)  	// restore original warning level

Example 2: "Unused function parameter." Check to make sure you really didn't mean to use the function parameter (e.g., it might be a placeholder for future expansion, or a required part of a standardized signature that your code has no use for). If it's not needed, simply delete the name of a function parameter:


// ... inside a user-defined allocator that has no use for the hint ...
// warning: "unused parameter 'localityHint'"
pointer allocate( size_type numObjects, const void *localityHint = 0 ) {
  return static_cast<pointer>( mallocShared( numObjects * sizeof(T) ) );
}
// new version: eliminates warning
pointer allocate( size_type numObjects, const void * /* localityHint */ = 0 ) {
  return static_cast<pointer>( mallocShared( numObjects * sizeof(T) )	);
}

Example 3: "Variable defined but never used." Check to make sure you really didn't mean to reference the variable. (An RAII stack-based object often causes this warning spuriously; see Item 13.) If it's not needed, often you can silence the compiler by inserting an evaluation of the variable itself as an expression (this evaluation won't impact run-time speed):

// warning: "variable 'lock' is defined but never used"
void Fun() {
  Lock lock;
  // ...
}
// new version: probably eliminates warning
void Fun() {
  Lock lock;
  lock;
  // ...
}

Example 4: "Variable may be used without being initialized." Initialize the variable (see Item 19).

Example 5: "Missing return." Sometimes the compiler asks for a return statement even though your control flow can never reach the end of the function (e.g., infinite loop, throw statements, other returns). This can be a good thing, because sometimes you only think that control can't run off the end. For example, switch statements that do not have a default are not resilient to change and should have a default case that does assert( false ) (see also Items 68 and 90):

// warning: missing "return"
int Fun( Color c ) {
  switch( c ) {
  case Red:	return 2;
  case Green:	return 0;
  case Blue:
  case Black:	return 1;
  }
}
// new version: eliminates warning
int Fun( Color c ) {
  switch( c ) {
  case Red:	return 2;
  case Green:	return 0;
  case Blue:
  case Black:	return 1;
  default:	assert( !"should never get here!" );	// !"string" evaluates to false
	return -1;
  }
}

Example 6: "Signed/unsigned mismatch." It is usually not necessary to compare or assign integers with different signedness. Change the types of the variables being compared so that the types agree. In the worst case, insert an explicit cast. (The compiler inserts that cast for you anyway, and warns you about doing it, so you're better off putting it out in the open.)

Exceptions

Sometimes, a compiler may emit a tedious or even spurious warning (i.e., one that is mere noise) but offer no way to turn it off, and it might be infeasible or unproductive busywork to rephrase the code to silence the warning. In these rare cases, as a team decision, avoid tediously working around a warning that is merely tedious: Disable that specific warning only, disable it as locally as possible, and write a clear comment documenting why it was necessary.

References

[Meyers97] §48 • [Stroustrup94] §2.6.2

12. Know when and how to code for concurrency.

Summary

Thsareafedly: If your application uses multiple threads or processes, know how to minimize sharing objects where possible (see Item 10) and share the right ones safely.

Discussion

Threading is a huge domain. This Item exists because that domain is important and needs to be explicitly acknowledged, but one Item can't do it justice and we will only summarize a few essentials; see the References for many more details and techniques. Among the most important issues are to avoid deadlocks, livelocks, and malign race conditions (including corruption due to insufficient locking).

The C++ Standard says not one word about threads. Nevertheless, C++ is routinely and widely used to write solid multithreaded code. If your application shares data across threads, do so safely:

Note that the above applies regardless of whether the type is some kind of string type, or an STL container like a vector, or any other type. (We note that some authors have given advice that implies the standard containers are somehow special. They are not; a container is just another object.) In particular, if you want to use standard library components (e.g., string, containers) in a multithreaded program, consult your standard library implementation's documentation to see whether that is supported, as described earlier.

When authoring your own type that is intended to be usable in a multithreaded program, you must do the same two things: First, you must guarantee that different threads can use different objects of that type without locking (note: a type with modifiable static data typically can't guarantee this). Second, you must document what users need to do in order to safely use the same object in different threads; the fundamental design issue is how to distribute the responsibility of correct execution (race-and deadlock-free) between the class and its client. The main options are:

Note that calling code should not need to know about your types' implementation details (see Item 11). If your type uses under-the-covers data-sharing techniques (e.g., copy-on-write), you do not need to take responsibility for all possible thread safety issues, but you must take responsibility for restoring "just enough" thread safety to guarantee that calling code will be correct if it performs its usual duty of care: The type must be as safe to use as it would be if it didn't use covert implementation-sharing. (See [Sutter04c].) As noted, all properly written types must allow manipulation of distinct visible objects in different threads without synchronization.

Particularly if you are authoring a widely-used library, consider making your objects safe to use in a multithreaded program as described above, but without added overhead in a single-threaded program. For example, if you are writing a library containing a type that uses copy-on-write, and must therefore do at least some internal locking, prefer to arrange for the locking to disappear in single-threaded builds of your library (#ifdefs and no-op implementations are common strategies).

When acquiring multiple locks, avoid deadlock situations by arranging for all code that acquires the same locks to acquire them in the same order. (Releasing the locks can be done in any order.) One solution is to acquire locks in increasing order by memory address; addresses provide a handy, unique, application-wide ordering.

References

[Alexandrescu02a][Alexandrescu04][Butenhof97][Henney00][Henney01][Meyers04][Schmidt01][Stroustrup00] §14.9 • [Sutter02] §16 • [Sutter04c]

34. Prefer composition to inheritance.

Summary

Avoid inheritance taxes: Inheritance is the second-tightest coupling relationship in C++, second only to friendship. Tight coupling is undesirable and should be avoided where possible. Therefore, prefer composition to inheritance unless you know that the latter truly benefits your design.

Discussion

Inheritance is often overused, even by experienced developers. A sound rule of software engineering is to minimize coupling: If a relationship can be expressed in more than one way, use the weakest relationship that's practical.

Given that inheritance is nearly the strongest relationship we can express in C++, second only to friendship, it's only really appropriate when there is no equivalent weaker alternative. If you can express a class relationship using composition alone, you should prefer that.

In this context, "composition" means simply embedding a member variable of a type within another type. This way, you can hold and use the object in ways that al- low you control over the strength of the coupling.

Composition has important advantages over inheritance:

Of course, these are not arguments against inheritance per se. Inheritance affords a great deal of power, including substitutability and/or the ability to override virtual functions (see Items 36 through 39, and Exceptions below). But don't pay for what you don't need; unless you need inheritance's power, don't endure its drawbacks.

Exceptions

Do use public inheritance to model substitutability. (See Item 37.)

Even if you don't need to provide a substitutability relationship to all callers, you do need nonpublic inheritance if you need any of the following, in rough order from most common (the first two points) to exceedingly rare (the rest):

References

[Cargill92] pp. 49-65, 101-105 • [Cline99] §5.9-10, 8.11-12, 37.04 • [Dewhurst03] §95 • [Lakos96] §1.7, §6.3.1 • [McConnell93] §5 • [Stroustrup00] §24.2-3 • [Sutter00] §22-24, §26-30 • [Sutter02] §23

Bibliography

Note: For browsing convenience, this bibliography is also available online. The bold references are hyperlinks in the online bibliography.

[Alexandrescu02a] A. Alexandrescu. "Multithreading and the C++ Type System" (InformIT website, February 2002).

[Alexandrescu04] A. Alexandrescu. "Lock-Free Data Structures" (>cite>C/C++ Users Journal, 22(10), October 2004).

[Butenhof97] D. Butenhof. Programming with POSIX Threads (Addison- Wesley, 1997).

[Cargill92] T. Cargill. C++ Programming Style (Addison-Wesley, 1992).

[Cline99] M. Cline, G. Lomow, and M. Girou. C++ FAQs (2nd Edition) (Addison-Wesley, 1999).

[Dewhurst03] S. Dewhurst. C++ Gotchas (Addison-Wesley, 2003).

[Henney00] K. Henney. "C++ Patterns: Executing Around Sequences" (EuroPLoP 2000 proceedings).

[Henney01] K. Henney. "C++ Patterns: Reference Accounting" (EuroPLoP 2001 proceedings).

[Lakos96] J. Lakos. Large-Scale C++ Software Design (Addison-Wesley, 1996).

[McConnell93] S. McConnell. Code Complete (Microsoft Press, 1993).

[Meyers97] S. Meyers. Effective C++ (2nd Edition) (Addison-Wesley, 1997).

[Meyers04] S. Meyers and A. Alexandrescu. "C++ and the Perils of Double- Checked Locking, Part 1" and "�Part 2" (Dr. Dobb's Journal, 29(7,8), July and August 2004).

[Schmidt01] D. Schmidt, M. Stal, H. Rohnert, F. Buschmann. Pattern- Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects (Wiley, 2001).

[Stroustrup94] B. Stroustrup. The Design and Evolution of C++ (Addison- Wesley, 1994).

[Stroustrup00] B. Stroustrup. The C++ Programming Language (Special 3rd Edition) (Addison-Wesley, 2000).

[Sutter00] H. Sutter. Exceptional C++ (Addison-Wesley, 2000).

[Sutter02] H. Sutter. More Exceptional C++ (Addison-Wesley, 2002).

[Sutter04c] H. Sutter. "'Just Enough' Thread Safety" (C/C++ Users Journal, 22(9), September 2004).

About the Authors

Herb Sutter is the author of three highly acclaimed books, Exceptional C++ Style, Exceptional C++, and More Exceptional C++ (Addison-Wesley). He chairs the ISO C++ standards committee, and is contributing editor and columnist for C/C++ Users Journal. As a software architect for Microsoft, Sutter leads the design of the C++ language extensions for .NET programming.

Andrei Alexandrescu is the author of the award-winning book Modern C++ Design (Addison-Wesley, 2001) and is a columnist for C/C++ Uses Journal.

The C++ Source | C++ Community News | Discuss | Print | Email | Screen Friendly Version | Previous | Next

Sponsored Links

Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us