C++ - Basics
This guide contains tips about writing C++ code.
C++ is a programming language that has many powerful features not present in other modern languages but managing those features brings additional complexity.
Architecture
Header files end with .h
. Headers contain function and class declarations with usage comments.
Implementation files end with .cpp
. Implementation files contain function and class definitions with implementation comments.
Use #include
to import code outside of the current file. #include
statement will be replaced with the content of the target file. These directives that start with the #
are called preprocessor commands. There are two forms of #include
, angle-bracket <>
and quote ""
form. Angle-bracket form is for system files while quote form is for other files.
// Angle-bracket form:
#include <iostream>
// 1. Search paths given to compiler.
// 2. If ran from command line, search standard system directories.
// Quoted form:
#include "filename.h"
// 1. Search the directory of the current file.
// 2. Search the directories of previously quote form included files.
// 3. Search paths given to compiler.
// 4. Search standard system directories.
Don't use outdated .h
header files with standard libraries. Whether you can use the new ones depends on your C++ version.
// bad
#include <iostream.h>
// good
#include <iostream>
Include starting from project path source.
// gaagle-awesome-project/src/base/logging.h
#include "base/logging.h"
Don't specify the path in standard library includes. These directory paths should be specified in a makefile.
// bad
#include <sys/limits.h>
// good
cc -I$(INCLUDE) -I$(INCLUDE)/sys
#include <limits>
Programs start from the main
function. The main function returns an integer that defines if an error occurred. Everything over 0
is considered an error.
#include <stdio.h>
int main()
{
char myValue[80];
printf("Enter value: ");
scanf("%79s", myValue);
printf("Value: %s", myValue);
return 0;
}
#include <iostream>
using namespace std;
int main()
{
string myValue;
// `cout` means console output, stream-based output.
// `<<` is insertion operation.
// `endl` means end of line.
cout << "Enter value:" << endl;
// `cin` means console input, stream-based input.
// `>>` is extraction operation.
cin >> myValue;
cout << "Value: " << myValue << endl;
return 0;
}
Read how your C++ compiler works. Compilers utilize many tricks to make your code run smoothly. Sometimes, they create problematic bugs. Only way to fix them is to know show compilers work.
// Dead Code Elimination
// This code generate almost no assembly when compiled with optimization.
// It sees that the value `myLong` is not read so it does not bother
// calculating it. So, to benchmark code, remember to `printf()` the result.
int main() {
long long myLong = 0;
for (long long i = 1; i <= 1000000000; ++i)
{
myLong += i;
}
}
Treat all compiler warnings as errors. Fix them as you encounter them. Configure warnings according to your project coding guidelines. This is done by changing compiler flags.
Use cpplint
to find style errors. Configure it according to your project style guidelines.
Portability
Rely on standard language and third party libraries. Avoid features provided by operating system or environment.
Don't exceed ISO C++ standard translation limit. It will not compile on all systems.
Don't use compiler specific language extensions.
Avoid inlining assembly code.
Header Files
Each implementation file .cpp
should have a matching header file .h
. There are some exceptions like tests and project initialization file.
// Following things go in header files.
const float SUPER = 3.0; // constants
enum Level { LOW, MEDIUM, HIGH }; // enumerations
int a; // data declarations
int getFoo(int); // function declarations
int getBar(char* s) { return (*s)++; } // inline function definition
class Orc; // class types
struct Line {float x; float y;}; // structure types
template <class T> // templates in this scope
Don't place data definitions in header files. Header file is for declaration, not definition.
// bad
int x = 100;
// good
int x;
Use define safeguards in header files to prevent multiple inclusion. Macro should be named PROJECT_PATH_FILE_H.
// in zookeeper/src/models/gorilla.h
#ifndef ZOOKEEPER_MODELS_GORILLA_H
#define ZOOKEEPER_MODELS_GORILLA_H
// ... header content ...
#endif // ZOOKEEPER_MODELS_GORILLA_H
Use _inline.h
files. If inline functions grow in numbers or size, or you use lots of templates, consider creating separate _inline.h
file to contain those definitions and declarations. Add a separate #define
guard.
Code Layout
Choose a formatting style and stick to it. There are as many formatting styles as there are programmers. Whichever you use is up to you to decide but be consistent inside each project. I prefer Allman Extreme so these notes use it.
/**
* Allman Extreme
*
* Main focus is to keep the most relevant information as far left on
* the screen as possible, like the normal Allman does.
*
* Having everything on the left increases skimming speed because people
* read top->down and left->right and you do not need to read each
* single line to the end.
*
* All opening braces `{` go on a new line for consistency.
*
* Everything inside a scope `{}` is indented to emphasis logical structure,
* except for class access levels, namespaces and switch-statements.
*
* Parentheses `()` stay as close to the containing statement as possible.
*/
Prefer 4 space indention over tabs. The most important things is to be consistent within the project.
Limit lines to 80 characters. More about the limit in master coding guide.
Always end files with an empty line. Some tools misbehave when file doesn't end in an empty line and helps with version control merges.
Use whitespace to tidy your code. But keep an eye on the vertical space your code takes, over 30 line code blocks are harder to skim through. Horizontal white space should be used to group related symbols together.
// bad
a=(b+c)*d;
for(i=0;i<10;i++){
// c++;
}
// good
a = (b + c) * d;
for (i = 0; i < 10; i++)
{
// c++;
}
Make incompleteness of statements obvious. Use indention and line breaks to show logical structure. When a statement goes to multiple lines, start all additional lines with a symbol that is invalid or rare to start a new statement.
#include <iostream>
using namespace std;
int reallyReallyReallyLongFunctionNameThatMakesYouCry(
int parameterNameIsLong,
int parameterNameIsSoLong,
int parameterNameIsTooLong)
{
return parameterNameIsLong + parameterNameIsSoLong
+ parameterNameIsTooLong;
}
int main()
{
int thisOneThing = 1;
int thatOneThing = 0;
bool aThirdThing = false;
bool theFourthThing = false;
bool yetAnother = true;
bool lastOne = true;
if (thisOneThing > thatOneThing
&& (aThirdThing == theFourthThing && yetAnother)
&& lastOne)
{
string commonExpression = "Hello World!";
cout << commonExpression << endl;
}
}
Naming
Variables are named in camelCase. Consider starting name with is
, has
, can
and should
when naming booleans.
// good
string tableName = "Clients";
uint32 timeoutMsecs = 10231;
bool isLocked = true;
Constants, enumerated values, defines and macros are in UPPER_SNAKE_CASE. Avoid using global variables though.
// good
::GLOBAL_CONSTANT;
#define IS_ERR(err) ...
MY_MACRO_THAT_SCARES_SMALL_CHILDREN
Class, structure and enumerated types are named in PascalCase. Enumeration values are in UPPER_SNAKE_CASE, prefixed by a common identifier.
// good
class MyClass
{
// ...
};
struct UrlTableProperties
{
// ...
};
typedef map<int, string> PropertyNameMap;
enum Color
{
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE
};
Methods and other object-oriented functions are in camelCase. Avoid using classless functions, but they are in lower_snake_case.
// good
addTableEntry();
deleteUrl();
openFileOrDie();
matrix.getElement(2, 4);
matrix.setElement(2, 4, element);
// acceptable if C or a global function
this_is_a_function();
Namespaces are in lower_snake_case. Choose namespace name based on the project name and optionally the path.
// project/src/gui/render.h
namespace project_gui
{
class Render
{
public:
void Show();
}
}
// project/src/gui/render.cpp
namespace project_gui
{
void Render::Show()
{
// ...
}
}
Don't declare anything in namespaces you haven't created e.g. in std
. Just #include
or using
them. You might overwrite some standard library functionality.
// This might break your program, or it might not.
namespace std
{
void foo(int)
{
// ...
}
}
Consider using namespace aliases if the names get long.
// good
namespace avln = a::very::longish::namespace;
class YourClass:
public avln::TheirClass
{
// ...
};
Files should be named according to the content. Most files should contain only one class, thus file name should be identical to the class name. If one class is in multiple files, use underscore to separate section name. If the file does not contain a class or contains multiple classes, file name shouldn't start with upper case letter.
// bad
myusefulclass.h
myusefulclass.cpp
my_other_useful_class.h
my_other_useful_class.cpp
Main.cpp
// good
MyUsefulClass.h
MyUsefulClass.cpp
MyOtherUsefulClass.h
MyOtherUsefulClass_load.cpp
MyOtherUsefulClass_save.cpp
MyOtherUsefulClass_transform.cpp
main.cpp
Don't use Hungarian notation. Don't specify variable type like integer or string in the variable name. You are using a compiler that has a job to notify you if you use variables wrong and most modern IDEs warn about wrong usage.
// bad, if `n` means number.
int nDoor
// bad, if `i` means integer.
int iDoor
// good
int doorCount
Use as descriptive names as required. Do not use abbreviations if not common knowledge e.g. URL is common knowledge, ESP is not. Optimal name length depends on the target scope e.g. iterators can be named i
, j
, k
, l
in max five line loops.
// bad
int nerr;
int errorCnt;
int nCompConns;
int wnHe;
// good
int errorNumber;
int errorCount;
int completedConnectionsCount;
int windowHeight;
Comments
Prefer //
comments. There should be blank line before every non-trailing comment. Use /* */
for bigger multiline comments and documentation.
Comment every file, class and public method:
- Usage and example comments go to the header files.
- Implementation related comments go to implementation files.
- Every file should start with a comment about the contents and what is the license for this code.
- Every class must have a comment what it is and a simple usage example. Usually same as the file comment.
- Every functions must have a comment about inputs, outputs, possible memory management details and usage help.
Prefer renaming variables over commenting. Comment if needed or must notify about something
// bad
private:
int totalEntries; // Total Number of Entries
// good
private:
// Keeps track of the total number of entries in the table.
// Used to ensure we do not go over the limit. -1 means
// that we don't yet know how many entries the table has.
int totalEntriesCount;
Use TODO
comments. Add TODO
comments if you’re doing good-enough coding, which you should be doing. Especially parts that can be optimized for better performance.
// good
// TODO: Use a "*" here for concatenation operator.
Variables
Line of code that creates a variable is called a declaration. Assigning a value to the variable is called definition.
#include <iostream>
using namespace std;
int main()
{
int myVariable; // declaration,
// will throw an error if removed
myVariable = 20; // definition/initialization/assignment,
// will have undefined value if removed, usually 0
cout << myVariable << endl;
return 0;
}
Prefer defining variables at declaration. Reduces risk that you use an uninitialized value by accident.
// good
int i = 0;
Postpone variable declaration and definition. Define variables close where they are first used.
// bad
int j = account.getDeficit();
// 30 lines of code that does not use j.
account.addDeposit(j);
// good
// 30 lines of code that does not use j.
int j = account.getDeficit();
account.addDeposit(j);
Declare each variable on a separate line. Reduces risks of error, faster to read and makes copy-pasting easier.
// bad
int* i, j;
// good
int* i;
int j;
Don't hide variables in nested scopes.
// bad
int i;
void foo()
{
int i; // hides the previous `i`
i = 10;
}
Literals are the values. Meaning of a literal cannot be changed, 3 is always 3 no matter the context. Literals are usually inside the variables.
3 = 2; // error
Avoid auto
and register
keywords. Compilers do better job without them and it doesn't hurt to specify clearly what you are working with.
// bad
auto iter = m.find(val);
Avoid extern
keyword. extern
means external linkage, declaring without defining. extern
can also be used to bring variable outside local scope of function. Don't write extern
where it is implicit.
// Header file:
const float s = 3.0E8F; // internal linkage constant definition
extern int a; // external linkage object declaration
int foo(); // external linkage function declaration
// Implementation file:
int a = 2; // external linkage object definition
Prefer pre-increment ++i
over post-increment i++
. Pre-increment usually has better performance because post- requires copying.
Don't assume order of evaluation in an expression.
// Either ++i could be evaluated first.
x = foo(++i, ++i);
Watch out for digraphs and trigraphs. Avoid using ??
, <%
and <:
.
// bad
// Here the ??/??/?? becomes \\?? as ??/ => \
cout << "Enter date ??/??/??";
// Here the <::std::pair becomes [:std::pair as <: => [
::std::vector<::std::pair<int, int> > vector_of_pairs;
Place non-member functions and global variables to a unnamed namespace so they don't pollute the global scope. You can create a "global" in that unnamed namespace. Avoid any "magic numbers".
// bad
namespace
{
const int MAX_PEOPLE = 10; // not accessible outside the namespace
}
Prefer explicit namespace access over using
directive.
namespace project_gui_help
{
const double BOOK_VERSION = 2.0;
}
// bad
using namespace project_gui_help;
cout << BOOK_VERSION;
// bad
using project_gui_help::BOOK_VERSION
cout << BOOK_VERSION;
// good
cout << project_gui_help::BOOK_VERSION
Prefer singletons over multiple complex global variables.
// good
class Singleton {};
Singleton& getSingleton()
{
static Singleton singleton;
return singleton;
}
Constants
Constant is a variable that cannot be changed. const
can be used in other contexts as well e.g. with functions and members. Improves compiler optimization.
const int legCount = 2;
Prefer using constant variables when possible. Compilers will love you for it.
// good
const int number // constant integer
const int* number // pointer to constant integer
int* const number // constant pointer to integer
const int *const number // constant pointer to constant integer
// Normal pointer can be redirected to point to other addresses.
// Constant pointer always points to the same address.
// Pointer to a normal value can change the value it is pointing to.
// Pointer to a constant value treats the value as constant even if it
// is not, and thus cannot change the value it is pointing to.
All global and static data should be constant. With exception that singleton pattern can use static data to ensure only one instance is created.
Use suffixes L, U and UL.
// bad
const unsigned int b = 0u;
const unsigned int c = 0;
const long e = 0l;
const long f = 0;
const unsigned long h = 0Ul;
const unsigned long i = 0;
// good
const unsigned int a = 0U;
const long d = 0L;
const unsigned long g = 0UL;
Use suffices F and L.
// bad
const long double R = 0.003;
const long double Z = 0.0l;
// good
const float PI = 3.1415F;
const long double A = 0.0L;
Use constant parameters. If a method does not modify parameter passed by reference or by pointer, that parameter should be const
.
Use constant methods. Methods should be const
if they do not modify any data members, do not call any non-const
methods, and do not return a non-const
pointer or non-const
reference to a data member.
Use constant member variables. Consider making data members const
whenever they do not need to be modified after constructor.
Casting
Casting is used to convert a value from one data type to another.
#include <iostream>
using namespace std;
int main()
{
int x = (int)(3.4 + 3.3); // explicit cast
cout << x << endl; // => 6
return 0;
}
Minimize casting all around. Avoid pointer casting. Avoid reference casting. Casting indicates poor design.
Prefer casting using static_cast
. Avoid const_cast
, reinterpret_cast
or dynamic_cast
. A lot easier to read and search in the code base than (type)value
syntax.
// static_cast: explicit type cast, usually acceptable
// const_cast: remove constant declaration, DANGEROUS
// reinterpret_cast: integer and pointer conversions, DANGEROUS
// dynamic_cast: class casting at runtime, DANGEROUS
// usually ok, but not the optimal way
float floatValue = (int)intValue;
// good
floatValue = static_cast<float>(intValue);
Avoid casting away volatile qualification. Causes invalid optimization.
Avoid casting away constant qualification. Leads to unexpected behavior.
Never rely on implicit casts. Implicit casts are easily left in function call arguments, prefer overloading the function. May cause hard to debug bugs.
floatValue = intValue; // implicit casting, bad
Don't cast floats to integers except through standard library routines. Leads to undefined behavior.
Data Types
Every variable has a data type. Data type describes the range of possible values that the variable may contain.
Prefer signed values over unsigned. Casting signed and unsigned can lead to unexpected results.
You can assume int
is at least 32 bits. If you need variable for big numbers, use int64_t
or uint64_t
from stdint
. Avoid using unsigned versions, if you don't have good reason for them.
Specify precision for floating point values unless you need double values.
// bad
float f = 0.5;
float f = 1.f;
// good
float f = 0.5f;
float f = 1.0f;
Don't expect floats calculations to yield exact results.
// bad
if (f != 3.142) {}
// good
if (double_equal(f, 3.142)) {}
Defined data types are just aliases for existing types. Declare only one custom data type in each typedef
.
// bad
typedef int* Pointer, Value;
// good
typedef int* Pointer;
typedef int Value;
Improve type safety with custom types or classes. Consider using classes or defined data types for scalar quantities to achieve better type safety.
// bad
long startTime;
// good
typedef long TimeStamp;
TimeStamp startTime;
// best
#include "TimeStamp.h";
TimeStamp startTime;
Don't rely on enumerator values. Don't cast integers to enumerators.
enum Colour
{
COLOR_RED = 0xA,
COLOR_GREEN,
COLOR_BLUE
};
Colour colour = COLOR_GREEN;
// bad
if ( 11 == colour )
{
}
// good
if ( COLOR_BLUE == colour )
{
}
Don't use plain char
in numeric operations. Always use explicit signed char
or unsigned char
.
// good
typedef unsigned char Byte;
Strings are arrays of characters.
#include <iostream>
using namespace std;
int main()
{
string myString = "Hello"; // 0 = H, 1 = e, 2 = l, 3 = l, 4 = o
int len = myString.length();
cout << len << endl; // => 5
int leftLPos = myString.find('l');
cout << leftLPos << endl; // => 2
int rightLPos = myString.rfind('l');
cout << rightLPos << endl; // => 3
int uPos = myString.rfind('u');
if (uPos == string::npos)
{
cout << "'u' was not found" << endl;
}
return 0;
}
Strings end with a null terminator \0
. But this is not usually shown when using strings.
#include <iostream>
using namespace std;
int main()
{
char myChars[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
string myString = (string)myChars;
cout << myString.length() << endl; // => 5
return 0;
}
Use only standard escape sequences.
Name ASCII C++
-----------------------------------------
newline NL(LF) \n
horizontal tab HT \t
vertical tab VT \v
backspace BS \b
carriage return CR \r
form feed FF \f
alert BEL \a
backslash \ \\
question mark ? \?
single quote ' \'
double quote " \"
octal number ooo \ooo
hex number hhh \xhhh
null terminator \0
Use c_str
member of strings to get a const char*
to use with legacy code.
Pointers And References
Pointers and references are used to refer objects indirectly. I personally prefer placing pointer indicator *
next to variable type e.g. int* x;
.
// Definition
int legs = 2; // legs is an integer 2.
int* ptrLegs = &legs; // ptrLegs is "pointer to integer", address of legs.
int& refLegs = legs; // refLegs is "reference to integer", a link to legs.
// Accessing
*ptrLegs = 4;
refLegs = 4;
// Reference binding
int legs = 3; // legs is an integer 3.
int& refLegs = legs; // refLegs is "reference to integer" named "legs".
int arms = 5; // arms is an integer 5.
refLegs = arms; // Does not change the "refLegs" but changes "legs".
// Examples
int* x[10]; // Array with 10 elements of type "pointer to integer".
int* f(char); // Function returning a pointer to integer.
int& g(char); // Function returning a reference to a integer.
int (*f)(char); // Pointer to a function returning an integer.
int (&g)(char); // Reference to a function returning an integer.
// Example with ++ operator
MyClass operator++(MyClass d); // avoid, does not change the d object
MyClass operator++(MyClass* d); // avoid, requires silly usage e.g. ++&x
MyClass& operator++(MyClass& d) // best, natural usage syntax and behaviour
Constant keyword can refer to the type or the modifier. If const
is after *
, the pointer is constant. If const
is before *
, the type is constant. References are a lot like constant pointers.
int* ptrLegs = legs; // pointer to integer
const int* ptrLegs = legs; // pointer to constant integer
int* const ptrLegs = legs; // constant pointer to integer
const int* const ptrLegs = legs; // constant pointer to constant integer
// You can declare reference to constant even if the target is not constant.
int number = 255; // Non-constant integer.
number = 2 * number; // OK
++number; // OK
const int& refNumber = number; // Reference to constant.
refNumber = 0; // Error
++refNumber; // Error
Have consistency in declaration specifiers. Compiler does not care in what order the declaration specifiers are, but you should.
// bad
int const& indians = i;
const int& romans = j;
// good
const int& indians = i;
const int& romans = j;
Pointer
Pointers can be re-assigned.
int x = 5;
int y = 6;
int* p; // dereference operator
p = &x; // reference operator
p = &y;
*p = 10;
assert(x == 5);
assert(y == 10);
Pointer can be assigned to "point nowhere". This is called a null pointer. This means that the pointer is not pointing to any memory address.
// C++ null pointer
int* myPtr = NULL; // becomes 0
// C++11 null pointer
int* myPtr = nullptr;
Pointer needs to be dereferenced with *
to access the location itself.
Pointer to classes and structs uses arrow ->
to access the members.
Pointers can be placed in an array.
Constant pointers cannot be bound to temporaries.
int* y = &int(12); // Illegal to dereference a temporary.
Avoid pointers to members. Syntax is obscure and there are compiler inconsistencies.
Reference
References are kind of like constant pointer. Compiler applies *
operator for you. References should be used when there cannot be re-assignment or cannot refer to NULL.
// Cannot be re-assigned.
int x = 5;
int y = 6;
int& reffy = x;
// You cannot make "reffy" to refer "y" in any way.
// Cannot refer to NULL.
int& reffy = NULL; // Error
You cannot extract the address of a reference.
References to classes and structs use dot .
to access the members.
References cannot be placed in an array.
Constant references can be bound to temporaries. So they can be used for argument lists.
const int& x = int(12); // Legal C++
Pointer vs Reference
C++ does not dictate how a compiler should implement references. All current compilers implement references as pointers so they take same amount of memory and have the same performance.
Use pointers to implement algorithms and data structures. Pointer usage syntax is awkward and error prone.
Use references in function parameters and return types. References are much faster than copying.
// bad
Student returnStudent(Student s) {
return s;
};
// good, if student cannot be changed outside
const Student& getStudent() {
// ...
};
// good, if student cannot be changed inside
void setStudent(const Student& s) {
// ...
};
Smart pointers. Smart pointers are wrapper objects that act like pointer but automate the underlying memory management. Consider using smart pointers if you require pointers. Avoid auto_ptr
if possible.
// bad, always
auto_ptr
// good, if truely necessary to share ownership
shared_ptr<const T>
// good in all other cases
scoped_ptr
Data Structures
Avoid creating algorithms and data structures yourself. Prefer data structure functions over custom algorithms. Prefer standard library algorithms over custom loops. Leave those for the guys who know what they are doing.
Do not inherit from standard library data structures. Releasing memory might have unexpected behaviour.
Do not insert derived class to a data structure meant for the base class. May cause unexpected behaviour.
Simplest data structure is struct
, a record-style data structure. struct
is used to group members under one name.
#include <iostream>
using namespace std;
struct Product {
int weight;
float price;
};
int main()
{
Product p;
p.weight = 10;
cout << p.weight << endl; // => 10
return 0;
}
Never include functions in structures. You can add functions in struct
but that is just silly.
Prefer empty()
over size()
when checking if data structure empty.
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> myVector(0);
//myVector.add(10);
if (myVector.size() == 0)
{
cout << "Bad check!" << endl;
}
if (myVector.empty() == true)
{
cout << "Good check!" << endl;
}
return 0;
}
When possible, pre-allocate in data structures to save unnecessary work.
// bad
void badPushBackManyNumbers(vector<int>& vec)
{
for (int i = 0; i < 100; ++i)
{
vec.push_back(i);
}
}
// good
void goodPushBackManyNumbers(vector<int>& vec)
{
vec.reserve(vec.size() + 100);
for (int i = 0; i < 100; ++i)
{
vec.push_back(i);
}
}
Do not typedef
array types. Causes problems in bounds checking and deletion.
// bad
typedef int ArrayType[10];
int* array = new ArrayType;
delete array; // Should be delete[], causes heap corruption.
Use vector
and string
in place of dynamically allocated arrays. They are easier to maintain.
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<float> temperatures(3, 32.0f);
temperatures.push_back(20.0f);
for (vector<float>::iterator temperature = temperatures.begin();
temperature != temperatures.end();
++temperature)
{
cout << *temperature << endl; // => 32
}
// => 32, 32, 32, 20
return 0;
}
Do not use vector<bool>
. Does not work with standard algorithms.
Use &v[0]
when passing vectors to C type functions.
// bad
void badWayToUseCFunctionWithVector(vector<int>& vec)
{
functionTakingArrayOfInt(vec.begin());
}
// good
void goodWayToUseCFunctionWithVector(vector<int>& vec)
{
assert(false == vec.empty() && "doesnt work with empty vectors!");
functionTakingArrayOfInt(&vec[0]);
functionTakingPointerToArrayOfInt(&vec[0]);
}
Use map
to map keys to values.
#include <iostream>
#include <map>
using namespace std;
int main()
{
map<int, string> students;
students[1234] = "John";
students[5533] = "Peter";
cout << students[1234] << endl;
return 0;
}
Avoid using streams. You may consider using them for logging. Prefer printf
-like functions.
// bad
cout << this; // Prints the memory address.
cout << *this; // Prints the contents.
cerr << "Error connecting to '" << foo->bar()->hostname.first
<< ":" << foo->bar()->hostname.second << ": " << strerror(errno);
// good
fprintf(stderr, "Error connecting to '%s:%u: %s",
foo->bar()->hostname.first,
foo->bar()->hostname.second,
strerror(errno)
);
Conditionals
Prefer including curly brackets.
// bad
if (i != 0)
foo(i);
// even more bad...
if (i != 0)
bar(i);
foo(i);
// good
if (isValid())
{
run();
}
if (isValid()
&& thisIsSomethingReallyLong()
&& (!soIsThis()))
{
run();
}
Avoid using result of assignment operator.
// bad
if ((j = i) == 1)
{
// ...
}
// good
j = i;
if (1 == j)
{
// ...
}
Prefer placing pure variable on the left hand side of comparison. Protects against '=' vs '==' and bad operator==
overloads.
// bad
if (a == 10)
// good
if (10 == a)
Control Structures
Avoid using goto
. Unnecessary complexity as only a few people can use them in the right places.
Avoid long or complex ternary operators. They are harder to read than if
and add illusion of reduced complexity.
// good
isValid() ? proceed() : doCheckAgain();
// good
if (isValid())
{
run();
}
else
{
doCheckAgain();
}
Use explicit test of equality. Do not trust overloaded functions.
int bar();
// bad
if (bar())
{
// ...
}
// good
if (0 != bar())
{
// ...
}
Always include break
in a non-empty case
statement.
// bad
switch (i)
{
case 0:
case 1:
{
++i;
}
default:
{
i = 0;
break;
}
}
// good
switch (i)
{
case 0:
case 1:
{
++i;
break;
}
default:
{
i = 0;
break;
}
}
Avoid using break
and continue
in loops. May result in hard to read code.
Do not alter loop control variable more than one place in a loop statement. It is just really hard to follow what is happening
// bad
for (int i = 0; i != 10; ++i)
{
if (0 == i % 3)
{
++i;
}
}
Loop control variable should be tested against a constant value. Loop control variable can be created within the statement.
// bad
int i = -100;
for (i = 0; i < xxx.size(); ++i)
{
// ...
}
// good
const int listSize = xxx.size();
for (int i = 0; i < listSize; ++i)
{
// ...
}
Functions
All functions with similar name should have a similar behaviour.
Use same parameter names in function declaration and definition.
List inputs before outputs in function parameters. Even after modifications. Input parameters should be considered constant in most case.
// good
void foo(const string& inputOne, const string* inputTwo, string* outputOne);
Prefer constant parameters:
- Use constant pass-by-reference on input parameters that cannot be NULL.
- Use constant pass-by-pointer if input parameter can be NULL.
- Use pass-by-value only if required.
// good
void foo(const string& input); // pass-by-reference
void foo(const string* input); // pass-by-pointer
void foo(string input); // pass-by-value
Consider turning parameters passed-by-reference to constants.
// good
void foo(const string& input, string* output);
Avoid using ellipsis ...
in function parameters.
Function should never not return reference or pointer to local variable within the function.
Avoid default parameters in functions. Exception if you have variable-length parameter list, then you can use defaults.
Do not use default arguments in overloaded functions.
// bad
void bar(int);
void bar(int, char c=10);
// good
void foo(int);
void foo(int, char c);
void baz(int, char c=10);
Consider making static
member functions when behaviour is not bound to a particular class instance.
Inline Functions
An inline function is requesting compiler to insert the complete body of the function to every place the function is called. Defining a function in the class declaration changes it to an inline function. inline
keyword is a hint to the compiler and does not mean the function will actually be inlined.
// good
class MyCLass
{
public:
inline int getBar()
{
return 1;
}
};
Consider declaring only trivial functions inline
. Increases binary size if functions are more than few lines, which can make the code slower in the end.
Inline functions should be 10 lines or less. Good inline functions are accessors, mutators and other short performance critical functions. Bad inline functions contain loops and switches in main execution path.
Avoid using inline virtual functions. When an object is reference by pointer or reference, a call to virtual
function cannot be inlined as compiler does not yet know which function to insert.
// bad
class A
{
public:
virtual void foo() {}
};
// inlining foo...
Classes
Organize class definitions by access: public
, protected
, private
. Optionally add blank line between access levels.
class Person
{
public:
// public variables
// public methods
protected:
// protected variables
// protected methods
private:
// private variables
// private methods
};
Avoid using friend
. Do not make functions or classes friends if you do not have legitimate design reason for it. A class can declare other classes or functions as friend
s so they can access non-public class members.
// Friend declaration is good for:
// - Making internal state inspectable by test code.
// - Making builder class friends with built class, to modify states.
class Child
{
friend class Mother;
public:
string name();
protected:
void setName( string newName );
};
Make member data private if you have no reason to increase visibility. Avoid using implicit definitions.
// bad
class Base
{
public:
int b;
protected:
int c;
private:
int a;
};
// good
class Base
{
private:
int a;
int b;
int c;
};
Avoind nested classes. Means defining a class inside another class.
Define objects using direct initialization. Not all compilers can optimize it otherwise.
#include <iostream>
using namespace std;
int main()
{
string badOne = "Bad One ";
string badTwo = string("Bad Two ");
string goodOne("Good One ");
cout << badOne << badTwo << goodOne << endl;
return 0;
}
Define copy constructor, copy assignment and destructor for classes which manage resources. Compiler provided copying perform shallow copies where both will share the resources and on destruction same resource will be freed more than once.
Define all parts of memory management or rely fully on defaults. If a class defines a destructor, copy constructor or copy assignment operator then it should probably explicitly define all three, and not rely on the default implementation.
Consider using swap operation to implement copy assignment. Does not slice objects, handles self-assignment and is exception safe.
// good
class A
{
public:
A& operator=(const A& rhs)
{
A temp(rhs);
Swap(temp);
return *this;
}
private:
void Swap(A& rhs) throw ();
};
Aim that all classes provide minimal interface to work with. Default constructor, copy constructor, copy assignment and destructor. Other compiler generated functions are satisfactory if class has no pointer member variables and each of these implicitly generated functions may have public access. Public interface should only have functions.
// bad
class Xulu
{
public:
int getValue() const;
};
// good
class Xulu
{
public:
Xulu();
Xulu(const Xulu&);
Xulu& operator=(const Xulu&);
~Xulu();
int getValue() const;
};
Ensure that copy assignment behaves consistently with the build in assignment.
// good
A& A::operator=(const A& rhs)
{
if (this != &rhs) // 1.
{
releaseAllResources(this); // 2.
Base::operator=(rhs); // 3.
copyMemberData(this, rhs); // 4.
}
return *this; // 5.
}
/**
* 1. Prevent assignment to self. Potentially dangerous since resources
* will be released before assignment.
* 2. Release all resources owned by this object. Delete to any pointers
* that are referenced solely by this object.
* 3. __If__ this object is of a derived class, invoke assignment
* operator for the base class.
* 4. Copy all member data in the argument object according to the copy
* semantics for the class.
* 5. Return this object as a reference. Being consistent with the build-in
* assignment operators.
*/
Prefer constant methods. By placing const
after the function definition, the function is not allowed to change any class members, except ones that are marked mutable
.
// To declare as constant member function, place `const` after
// the argument list.
// `mutable` keyword can be used to declare member data which can
// be modified in constant functions. Again, should only be
// used if external state is not modified.
// bad
class Date
{
public:
int getMonth();
void setMonth(int month);
protected:
int month;
};
// good
class Date
{
public:
int getMonth() const;
void setMonth(int month);
protected:
int month;
};
Do not write methods which return non-constant pointers or references to data less accessible than the member function itself.
#include <stdio.h>
class Person
{
public:
Person() : id(0) {}
int& badRead() { return id; }
const int& goodRead() { return id; }
private:
int id;
};
int main()
{
Person p;
printf("Id is %d.\n", p.goodRead()); // => Id is 0.
p.badRead() = 10;
printf("Id is %d.\n", p.goodRead()); // => Id is 10.
p.goodRead() = 20; // error: assignment of read-only location
return 0;
};
Do not return non-constant handles to class data from constant methods. Constant methods are optimized with the expectation that those methods cannot change non-mutable values. Also causes the same problem as the previous.
#include <stdio.h>
class Person
{
public:
Person() { *id = 10; }
int* getIdBad() const { return id; }
const int* getIdGood() const { return id; }
private:
int* id;
};
int main()
{
const Person p;
printf("Id is %d.\n", *p.getIdBad());
int* pid = p.getIdBad();
*pid = 666; // can change internals of the object.
printf("Id is %d.\n", *p.getIdBad());
return 0;
};
Prefer returning constant objects. Unless usage of the object is to change its state.
Any behaviour should be implemented by only one function in a class.
// good
class Animus
{
public:
Animus operator+(const Animus& rhs)
{
Animus temp(*this);
temp += rhs;
return temp;
};
Animus& operator+=(const Animus& rhs)
{
// ...
};
};
Consider defining insertion operator <<
for all classes. Useful for debugging and testing.
Do not define conversion operators to fundamental types. Implicit conversions can take place without programmers knowledge.
class Voluntas;
class Animus
{
public:
operator Voluntas();
};
Do not define conversion operators for class types. Implicit conversions can take place without programmers knowledge.
// bad
class C;
class D
{
public:
D(C); // 1
};
class C
{
public:
operator D(); // 2
};
void foo(D);
void bar()
{
C c;
foo(c); // ambiguous, convert to D using 1 or 2?
}
Define or forbid copy operation on your classes. For custom classes, define copy operation for algorithms and data structures. If copying is too expensive, use container of pointers or smart pointers. Or disallow copy and equal check for the custom class.
// 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&)
class Foo
{
public:
Foo(int f);
~Foo();
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
};
Avoid dynamic class lookups. Avoid using Run Time Type Information (RTTI) to dynamically query object class. Suboptimal performance and most of the time means poor design.
Constructors and Destructors
Ensure that constructors supplies initial state for all base classes and non-static data members.
// good
/**
* Initialization order:
* 1. Virtual base classes as they appear in the inheritance list.
* 2. Base classes as they appear in the inheritance list.
* 3. Non-static member data in order of declaration.
*/
class BaseClass
{
// ...
};
class VirtualClass :
public virtual BaseClass
{
// ...
};
class AnotherClass
{
// ...
};
class DerivedClass :
public VirtualClass, public AnotherClass
{
public:
DerivedClass() :
BaseClass(), VirtualClass(), AnotherClass(), i(1), c()
{
// ...
}
private:
int i;
AnotherClass c;
};
Write constructor initialization list on order they are declared.
Write destructor initialization list in reverse order they are declared.
Consider making separate init
function if constructor gets complex. Constructor should always set initial data member values.
Declare single argument constructors as explicit
. Without explicit
, they can accidentally be used in casting.
// bad
class Base
{
public:
Base(double);
Base(float f, int i=0); // implicit conversion constructor
Base(int i=0, float f=0.0); // default constructor + conversion
};
// good
class Base
{
public :
Base(const Base&); // copy constructor
Base(); // default constructor
Base(int, int); // more than one non-default argument
explicit Base(int); // declared explicit
};
Do not declare public constructor for abstract classes. Guides against misuse.
Ensure destructors release all resources owned by the object. Will cause resource leaks otherwise.
Do not inline constructors or destructors. That is just silly. Takes up space for nothing.
Inheritance
Use only public inheritance. Public visibility means "is-a" relationship. Private visibility means "is-implemented-by" relationship and should always be done by composite pattern where a class contains instances of another class.
// bad
class A {};
class B : private A {};
class C : protected A {};
class D : A {}; // implicitly private
// good
class A {};
class E : public A {};
Do not derive from more than one not purely abstract class.
Write virtual destructor for base classes. If an object will be destroyed through a pointer its base class then base class should have a virtual destructor.
// bad
class Base
{};
class Derived :
public Base
{
public :
~Derived() {}
};
void callsDerivedDestructor()
{
Derived* d = new Derived;
delete d;
}
void problemDoesNotCallDerivedDestructor()
{
Derived* d = new Derived;
Base* b = d;
delete b;
}
Avoid downcasting base class object pointers to derived class. Instead use virtual functions in base class.
// bad
class Base {};
class Derived :
public Base
{
virtual void foo();
};
void foo()
{
Base* a = new Derived;
static_cast<Derived*>( a )->foo();
}
// good
class Base
{
virtual void bar();
};
class Derived :
public Base
{
virtual void bar();
};
void foo()
{
Base* a = new Derived;
a->bar();
}
Avoid upcasting to a virtual base class. If required, use static_cast
. This is usually irreversible.
// bad
class Base
{};
class Derived :
public virtual Base
{};
A* foo()
{
Derived* der = new Derived;
return static_cast<Base*>( der );
}
Override all base class virtual function overloads, if you override one. When an overload virtual function is overridden, the other base class overloads are hidden from the derived class.
// bad
class Base
{
public:
virtual void foo(short);
virtual void foo(double);
};
class Derived :
public Base
{
public:
virtual void foo(short);
void bar()
{
foo(0.1); // calls Derived::foo( short )
}
};
Do not overload or hide inherited non-virtual functions.
Remove unnecessary virtual definitions. If a virtual function in base class is not overridden in any derived classes, make it non-virtual. If a virtual function in base class is overridden the same way in all derived classes, make it non-virtual. Clearer and improves performance.
Consider using pure virtual declaration. Declare virtual function as pure virtual if each derived class must implement it.
// bad
class Base
{
public:
virtual int mustBeImplemented();
};
// good
class Base
{
public:
virtual int mustBeImplemented() = 0; // pure virtual
};
Ensure that virtual functions maintain compatible return type. Return type can be derived from the base class return type.
// bad
class Base
{
public:
virtual Base* clone()
{
return new Base(*this);
}
};
class Derived :
public Base
{
public:
virtual Derived* clone()
{
return new Derived(*this);
}
};
void fn(Derived* d, Base* b)
{
Derived* p1 = d->clone();
Derived* p2 = b->clone(); // error, downcast needed here
}
Virtual function overrides must use the same default parameter values.
// bad
class Base
{
public:
virtual void badvFn(int a = 0);
};
class Derived :
public Base
{
public:
virtual void badvFn(int a = 10);
};
void foo( Derived& obj )
{
Base& baseObj = obj;
// Uses default value from base even though calls derived function.
baseObj.badvFn(); // calls Derived::badvFn with a = 0
obj.badvFn(); // calls Derived::badvFn with a = 10
}
// good
class Base
{
public:
virtual void goodvFn(int a = 0);
};
class Derived :
public Base
{
public:
virtual void goodvFn(int a = 0);
};
void foo( Derived& obj )
{
Base& baseObj = obj;
// Ok - derived and base have the same default value.
baseObj.goodvFn(); // calls Derived::goodvFn with a = 0
obj.goodvFn(); // calls Derived::goodvFn with a = 0
}
Do not invoke virtual methods of the declared class in constructor or destructor.
// TODO: verify
class Base
{
public:
Base();
virtual void func();
};
class Derived :
public Base
{
Derived() : Base() {}
virtual void func();
};
Base::Base()
{
func(); // Base::func called not Derived::func
}
Declare copy assignment operator protected in an abstract class.
// bad
class BaseAbstract
{
public:
BaseAbstract& operator=(const BaseAbstract&);
};
class Derived :
public BaseAbstract
{
public:
Derived& operator=(const Derived&);
};
void foo()
{
Derived obj1;
Derived obj2;
BaseAbstract* ptr1 = &obj1;
BaseAbstract* ptr2 = &obj2;
*ptr1 = *ptr2;
}
Ensure that, base classes common to more than one derived class, are virtual. Can cause ambiguous instructions.
// bad
class Base
{
public:
void f();
};
class derivedLeft :
public Base
{};
class derivedRight :
public Base
{};
class Derived :
public derivedLeft,
public derivedRight
{};
void test() {
Derived d;
d.f();
}
// good
class derivedLeft :
public virtual Base
{};
class derivedRight:
public virtual Base
{};
Explicitly declare polymorphic member functions virtual in a derived class. Easier to understand when reading.
class Base
{
public:
virtual void f();
virtual void operator+(Base const&);
virtual ~Base();
};
class Derived :
public Base
{
public:
virtual void f();
virtual void operator+(Base const&);
virtual ~Derived();
};
Overloading
Name mangling occurs in C++ because of overloading. Since C++ has overloading of function names and C does not, the C++ compiler cannot just use the function name as a unique id to link to, so it mangles the name by adding information about the parameters. C compiler does not need to mangle the name since you cannot overload functions in C.
// How to disable name mangling, although not adviced.
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
Avoid function overloading. Consider naming functions so they give hint what they get for parameters.
// bad
const Animation* GetAnimation(int index) const;
const Animation* GetAnimation(const char* name ) const;
const Animation* GetAnimation(float randomDiversity) const;
Append(const String& addString);
Append(const int& addInteger);
// good
const Animation* GetAnimationByIndex(int index) const;
const Animation* GetAnimationByName(const char* name) const;
const Animation* GetRandomAnimation(float randomDiversity) const;
AppendString(const String& addString);
AppendInt(const int& addInteger);
Overloading by adding const
is allowable. So function can be called from constant functions
class AnimatedEntity :
public Entity
{
public:
Animator* GetAnimator(void);
const Animator* GetAnimator(void) const;
};
Ensure that overloaded operators have expected behaviour. Ensure that sequence of overloaded operations give equivalent behaviour as a sequence of their normal versions.
// operator+ does adding numbers or string concatenation.
// operator== and operator!= do object equivalence checks.
a += b // Same result as a = a + b.
a += 1 // Same result as ++a.
Avoid overloading operators ,
, &&
and ||
. Will cause problems because operators are not evaluated in same order in overridden variant.
Ensure that overloaded binary operators are non-member functions. Allows implicit conversion of the left hand operand.
// bad
class Complex
{
public:
Complex(float r, float i=0);
Complex operator+(const Complex& rhs);
};
void add()
{
Complex a(1, 0);
a = a + 2; // 2 is converted to Complex
a = 2 + a; // Error
}
// good
class Complex
{
// Make it `friend` only when it needs to access private members.
// Otherwise simply make it non-friend non-member function.
// float + complex
friend Complex& operator+(float op1, const Complex& op2);
public:
Complex& operator+(const Complex& op2); // complex + complex
Complex& operator+(float op2); // complex + float
}
When overloading subscript operator operator[]
implement both constant and non-constant versions.
class Array
{
public:
Array()
{
for (int i = 0; i < MAX_SIZE; ++i)
{
x[ i ] = i;
}
}
int& operator[] (const int a)
{
std::cout<< "nonconst" << std::endl;
return x[ a ];
}
int operator[] (const int a) const
{
std::cout << "const" << std::endl;
return x[ a ];
}
private:
enum { MAX_SIZE = 10 };
int x[MAX_SIZE];
};
int main()
{
Array a;
int i = a[ 3 ]; // Non-constant
a[ 3 ] = 33; // Non-constant
const Array ca;
i = ca[ 3 ]; // Constant
ca[ 3 ] = 33; // Error
return 0;
}
Error Handling
Use asserts to mark down assumptions. Helps to detect places where execution does not happen as the original development expected. Hard hard and early.
#include <assert.h>
using namespace std;
int main()
{
assert(true);
return 0;
}
Guard every division operations by checking for zeroes. Dividing by zero causes weird behaviour.
#include <iostream>
#include <assert.h>
using namespace std;
int main()
{
int divider = 0;
assert(0 != divider);
cout << 10/divider << endl;
cout << "LOL" << endl;
return 0;
}
Guard modulus operations by checking negatives.
#include <iostream>
#include <assert.h>
using namespace std;
int main()
{
int modulor = -3;
assert(modulor > 0);
cout << 10 % modulor << endl;
cout << "LOL" << endl;
return 0;
}
Consider whether to use exceptions. If you want to use exceptions, you need to use them from the start of the project. They are really troublesome to add in later.
- Exceptions are not a flow control mechanism. Exception means the program or library is crashing.
- If caller can possibly fix the error, consider throwing exceptions.
- If caller cannot fix the error, use assert and make sure it crashes.
Do not throw exceptions in destructors. The program will crash.
Custom exception class names should always end with word Exception
.
class MyException;
Only throw objects of class that derives from std::exception
.
#include <exception>
class MyException: public std::exception
{
public:
virtual const char* what() const throw()
{
return "My exception happened...";
}
}
Catch exceptions by reference. Pass by pointer requires extra calls for memory allocation.
Ensure that all orphaned resources are released when an exception is thrown. Ensure that all orphaned resources are released when the program terminates.
Create one base exception per project and per library. Your other exceptions should inherit from those, so catching exceptions from those sections of the program is easier.
Consider making a throw macro. Consider making macro for throwing exceptions that records current file, current line, timestamp and thread ID.
THROW_EXCEPTION_IF(cond, msg1, code);
Templates
Templates can be used to make following code to operate with varying types. This allows us to create a function template whose functionality can be adapted to more than one type or class without repeating the entire code for each type.
#include <stdio.h>
template <class T>
T getMax(T a, T b)
{
return (a > b ? a : b);
}
int main()
{
int x = 5;
int y = 10;
int max = getMax<int>(x, y);
printf("%d", max); // => 10
return 0;
}
Use templates only if behaviour is completely independent of object type.
// bad
template<typename T>
void foo(T t)
{
if (0 != dynamic_cast<SomeType*>(t))
{
// ...
}
}
// good
template<typename T>
void bar(T t)
{
t.someFunction();
}
Do not declare a class template with potentially conflicting methods.
// bad
template<typename T>
class Gorilla
{
public:
void foo(T);
void foo(int);
};
template class Gorilla<double>; // works fine
template class Gorilla<int>; // but now function foo(int) is declared twice.
Ensure that the template arguments fulfil interface requirements.
// bad
#include <stdio.h>
class Person
{
public:
Person() : age(18) {}
int getAge(void) { return age; };
private:
int age;
};
template<class T>
class UniqueExistance
{
public:
bool isMatch(T other) { return (singleton == other); }
private:
T singleton;
};
int main()
{
Person peasant;
printf("Age: %d", peasant.getAge()); // => Age: 18
UniqueExistance<Person> emperor;
if (emperor.isMatch(peasant))
{
// error: no match for ‘operator==’
}
return 0;
}
Memory Management
Prefer new
/delete
over malloc
/free
. Do not use C memory management malloc
, realloc
and free
. Allocate memory using new
and release using delete
.
Allocate and release using the right form of the function. There are different forms for array and single variables.
// bad
string* nameList = new string[10];
delete nameList;
string* name = new string;
delete[] name;
// good
string* nameList = new string[10];
delete[] nameList;
string* name = new string;
delete name;
Write operator delete
if you write the operator new
. Both are implicitly static
, so specify them as such.
Do not specify number of element when releasing array.
On delete
, always set pointer to zero after.
Automatically freed memory is not a magic bullet. You may consider using alloca()
if you keep it in single function scope and you are not storing much data. Consider using scoped_ptr/scoped_array
instead.
Prefer sizeof(variable)
over sizeof(type)
. Later can easily fall out of sync.
// bad
memset(&data, 0, sizeof(Person));
// good
memset(&data, 0, sizeof(john));
Preprocessor
Avoid macros. Prefer inline functions, enumerators and constant variables.
Do not define preprocessor macros in header files. Define macros just before you use them and undefine them right after if possible.
Write preprocessor directives consistently.
// bad
# ifdef SOME_FLAG
#define SOME_OTHER_FLAG
#endif
// good
#ifdef SOME_FLAG
# define SOME_OTHER_FLAG
#else
# define YET_ANOTHER_FLAG
#endif
Control conditional compilation by use of token definitions. Prefer #ifdef
and #ifndef
over #if
.
Use __cplusplus identifier to distinguish between C and C++.
#ifdef __cplusplus
extern "C" some_function();
#else
extern some_function();
#endif
Do not use comments in definition of preprocessor macro. These cause problems with some compilers. Place them before it.
Avoid function macros, use inline functions instead. If must, enclose macro arguments and body in parentheses.
// bad
#define BAD_SQ(A) A * A
int x = BAD_SQ(6 + 3); // expands to: 6 + 3 * 6 + 3
// acceptable
#define OK_SQ(A) ((A) * (A))
int y = OK_SQ(6 + 3); // expands to: ((6 + 3) * (6 + 3))
Avoid using #define
to define constants. Use constant variables and enumerators. Using define
obscurates error messages because the value is replaced.
// bad
#define ASPECT_RATIO 1.653
Avoid using a NULL
macro. Varying implementation may cause porting problems. Use 0 instead for pointers.
// good, in the project main header.
static const int nullptr = 0;
Sources
- Coding Standard
- Google C++ Guide
- id Software C++ Style Guide
- Effective C++, Scott Meyers
- Optimizing C++ Code : Dead Code Elimination
- Modern C++
- Rook's Guide to C++, Jeremy A. Hansen