CPP Best Practices
CPP Best Practices
of Contents
Introduction 1.1
Preface 1.2
Use the Tools Available 1.3
Style 1.4
Considering Safety 1.5
Considering Maintainability 1.6
Considering Portability 1.7
Considering Threadability 1.8
Considering Performance 1.9
Enable Scripting 1.10
Further Reading 1.11
Final Thoughts 1.12
1
Introduction
cppbestpractices
This book has inspired an O'Reilly video: Learning C++ Best Practices
2
Preface
Preface
C++ Best Practices: A Forkable Coding Standards Document
In all cases brevity and succinctness is preferred. Examples are preferred for making the
case for why one option is preferred over another. If necessary, words will be used.
C++ Best Practices by Jason Turner is licensed under a Creative Commons Attribution-
NonCommercial 4.0 International License.
Disclaimer
This document is based on my personal experiences. You are not supposed to agree with it
100%. It exists as a book on GitHub so that you can fork it for your own uses or submit back
proposed changes for everyone to share.
This book has inspired an O'Reilly video: Learning C++ Best Practices
3
Use the Tools Available
Source Control
Source control is an absolute necessity for any software development project. If you are not
using one yet, start using one.
GitHub - allows for unlimited public repositories, must pay for a private repository.
Bitbucket - allows for unlimited private repositories with up to 5 collaborators, for free.
SourceForge - open source hosting only.
GitLab - allows for unlimited public and private repositories, unlimited CI Runners
included, for free.
Visual Studio Online (http://www.visualstudio.com/what-is-visual-studio-online-vs) -
allows for unlimited public repositories, must pay for private repository. Repositories can
be git or TFVC. Additionally: Issue tracking, project planning (multiple Agile templates,
such as SCRUM), integrated hosted builds, integration of all this into Microsoft Visual
Studio. Windows only.
Build Tool
Use an industry standard widely accepted build tool. This prevents you from reinventing the
wheel whenever you discover / link to a new library / package your product / etc. Examples
include:
CMake
Consider: https://github.com/sakra/cotire/ for build performance
Consider: https://github.com/toeb/cmakepp for enhanced usability
Utilize: https://cmake.org/cmake/help/v3.6/command/target_compile_features.html
for C++ standard flags
Consider: https://github.com/cheshirekow/cmake_format for automatic formatting of
your CMakeLists.txt
See the Further Reading section for CMake specific best practices
Waf
4
Use the Tools Available
FASTBuild
Ninja - can greatly improve the incremental build time of your larger projects. Can be
used as a target for CMake.
Bazel - Note: MacOS and Linux only.
gyp - Google's build tool for chromium.
maiken - Crossplatform build tool with Maven-esque configuration style.
Qt Build Suite - Crossplatform build tool From Qt.
meson - Open source build system meant to be both extremely fast, and, even more
importantly, as user friendly as possible.
premake
Remember, it's not just a build tool, it's also a programming language. Try to maintain good
clean build scripts and follow the recommended practices for the tool you are using.
Package Manager
Package management is an important topic in C++, with currently no clear winner. Consider
using a package manager to help you keep track of the dependencies for your project and
make it easier for new people to get started with the project.
Continuous Integration
Once you have picked your build tool, set up a continuous integration environment.
Continuous Integration (CI) tools automatically build the source code as changes are pushed
to the repository. These can be hosted privately or with a CI host.
Travis CI
works well with C++
designed for use with GitHub
free for public repositories on GitHub
AppVeyor
supports Windows, MSVC and MinGW
free for public repositories on GitHub
Hudson CI / Jenkins CI
5
Use the Tools Available
go enable Travis Ci and AppVeyor integration right now. We'll wait for you to come back.
For a simple example of how to enable it for your C++ CMake-based application, see
here: https://github.com/ChaiScript/ChaiScript/blob/master/.travis.yml
enable one of the coverage tools listed below (Codecov or Coveralls)
enable Coverity Scan
These tools are all free and relatively easy to set up. Once they are set up you are getting
continuous building, testing, analysis and reporting of your project. For free.
Compilers
Use every available and reasonable set of warning options. Some warning options only work
with optimizations enabled, or work better the higher the chosen level of optimization is, for
example -Wnull-dereference with GCC.
You should use as many compilers as you can for your platform(s). Each compiler
implements the standard slightly differently and supporting multiple will help ensure the most
portable, most reliable code.
6
Use the Tools Available
GCC / Clang
-Wall -Wextra -Wshadow -Wnon-virtual-dtor -pedantic
-Wshadow warn the user if a variable declaration shadows one from a parent context
-Wnon-virtual-dtor warn the user if a class with virtual functions has a non-virtual
-Wlogical-op warn about logical operations being used where bitwise were probably
wanted
-Wnull-dereference warn if a null dereference is detected
-Wformat=2 warn on security issues around functions that format output (ie printf )
Consider using -Weverything and disabling the few warnings you need to on Clang
-Weffc++ warning mode can be too noisy, but if it works for your project, use it also.
MSVC
/permissive- - Enforces standards conformance.
/W4 /W14640 - use these and consider the following (see descriptions below)
of data
/w14263 'function': member function does not override any base class virtual member
function
/w14265 'classname': class has virtual functions, but destructor is not virtual instances
7
Use the Tools Available
/we4289 nonstandard extension used: 'variable': loop control variable declared in the
argument list
/w14546 function call before comma missing argument list
/w14547 'operator': operator before comma has no effect; expected operator with side-
effect
/w14549 'operator': operator before comma has no effect; did you intend 'operator'?
/w14928 illegal copy-initialization; more than one user-defined conversion has been
implicitly applied
Not recommended
/Wall - Also warns on files included from the standard library, so it's not very useful
General
Start with very strict warning settings from the beginning. Trying to raise the warning level
after the project is underway can be painful.
Consider using the treat warnings as errors setting. /Wx with MSVC, -Werror with GCC /
Clang
LLVM-based tools
LLVM based tools work best with a build system (such as cmake) that can output a compile
command database, for example:
$ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .
8
Use the Tools Available
If you are not using a build system like that, you can consider Build EAR which will hook into
your build system and generate a compile command database for you.
CMake now also comes with built-in support for calling clang-tidy during normal
compilation.
Static Analyzers
The best bet is the static analyzer that you can run as part of your automated build system.
Cppcheck and clang meet that requirement for free options.
Coverity Scan
Coverity has a free (for open source) static analysis toolkit that can work on every commit in
integration with Travis CI and AppVeyor.
PVS-Studio
PVS-Studio is a tool for bug detection in the source code of programs, written in C, C++ and
C#. It is free for personal academic projects, open source non-commercial projects and
independent projects of individual developers. It works in Windows and Linux environment.
Cppcheck
Cppcheck is free and open source. It strives for 0 false positives and does a good job at it.
Therefore all warnings should be enabled: --enable=all
Notes:
For correct work it requires well formed path for headers, so before usage don't forget to
pass: --check-config .
Finding unused headers does not work with -j more than 1.
Remember to add --force for code with a lot number of #ifdef if you need check all
of them.
cppclean
9
Use the Tools Available
cppclean - Open source static analyzer focused on finding problems in C++ source that slow
development of large code bases.
CppDepend
CppDepend Simplifies managing a complex C/C++ code base by analyzing and visualizing
code dependencies, by defining design rules, by doing impact analysis, and comparing
different versions of the code. It's free for OSS contributors.
clang-tidy can be easily used with Visual Studio via the Clang Power Tools extension.
Flint / Flint++
Flint and Flint++ are linters that analyze C++ code against Facebook's coding standards.
OCLint
OCLint is a free, libre and open source static code analysis tool for improving quality of C++
code in many different ways.
Cevelop
10
Use the Tools Available
The Eclipse based Cevelop IDE has various static analysis and refactoring / code fix tools
available. For example, you can replace macros with C++ constexprs , refactor
namespaces (extract/inline using , qualify name), and refactor your code to C++11's
uniform initialization syntax. Cevelop is free to use.
Qt Creator
Qt Creator can plug into the clang static analyzer.
clazy
clazy is a clang based tool for analyzing Qt usage.
Runtime Checkers
Code Coverage Analysis
A coverage analysis tool shall be run when tests are executed to make sure the entire
application is being tested. Unfortunately, coverage analysis requires that compiler
optimizations be disabled. This can result in significantly longer test execution times.
Codecov
integrates with Travis CI and AppVeyor
free for open source projects
Coveralls
integrates with Travis CI and AppVeyor
free for open source projects
LCOV
very configurable
Gcovr
kcov
integrates with codecov and coveralls
performs code coverage reporting without needing special compiler flags, just by
instrumenting debug symbols.
OpenCppCoverage - open source coverage reporting tool for Windows.
Valgrind
Valgrind is a runtime code analyzer that can detect memory leaks, race conditions, and other
associated problems. It is supported on various Unix platforms.
11
Use the Tools Available
Dr Memory
Similar to Valgrind. http://www.drmemory.org
AddressSanitizer
MemorySanitizer
ThreadSanitizer
UndefinedBehaviorSanitizer
Fuzzy Analyzers
If your project accepts user defined input, considering running a fuzzy input tester.
Both of these tools use coverage reporting to find new code execution paths and try to breed
novel inputs for your code. They can find crashes, hangs, and inputs you didn't know were
considered valid.
blog article.
Heap Profiling
https://epfl-vlsc.github.io/memoro/ - A detailed heap profiler
12
Use the Tools Available
Ignoring Warnings
If it is determined by team consensus that the compiler or analyzer is warning on something
that is either incorrect or unavoidable, the team will disable the specific error to as localized
part of the code as possible.
Be sure to reenable the warning after disabling it for a section of code. You do not want your
disabled warnings to leak into other code.
Testing
CMake, mentioned above, has a built in framework for executing tests. Make sure whatever
build system you use has a way to execute tests built in.
To further aid in executing tests, consider a library such as Google Test, Catch, CppUTest or
Boost.Test to help you organize the tests.
Unit Tests
Unit tests are for small chunks of code, individual functions which can be tested standalone.
Integration Tests
There should be a test enabled for every feature or bug fix that is committed. See also Code
Coverage Analysis. These are tests that are higher level than unit tests. They should still be
limited in scope to individual features.
Negative Testing
Don't forget to make sure that your error handling is being tested and works properly as well.
This will become obvious if you aim for 100% code coverage.
Debugging
uftrace
uftrace can be used to generating function call graphs of a program execution
rr
13
Use the Tools Available
Other Tools
Lizard
Lizard provides a very simple interface for running complexity analysis against a C++
codebase.
Metrix++
Metrix++ can identify and report on the most complex sections of your code. Reducing
complex code helps you and the compiler understand it better and optimize it better.
CNCC
Customizable Naming Convention Checker can report on identifiers in your code that do not
follow certain naming conventions.
ClangFormat
ClangFormat can check and correct code formatting to match organizational conventions
automatically. Multipart series on utilizing clang-format.
SourceMeter
SourceMeter offers a free version which provides many different metrics for your code and
can also call into cppcheck.
Bloaty McBloatface
Bloaty McBloatface is a binary size analyzer/profiler for unix-like platforms
14
Use the Tools Available
15
Style
Style
Consistency is the most important aspect of style. The second most important aspect is
following a style that the average C++ programmer is used to reading.
C++ allows for arbitrary-length identifier names, so there's no reason to be terse when
naming things. Use descriptive names, and be consistent in the style.
CamelCase
snake_case
are common examples. snake_case has the advantage that it can also work with spell
checkers, if desired.
Every IDE and many editors have support for clang-format built in or easily installable with
an add-in.
VSCode https://marketplace.visualstudio.com/items?itemName=xaver.clang-format
VisualStudio https://marketplace.visualstudio.com/items?
itemName=LLVMExtensions.ClangFormat#review-details
Resharper++:
https://www.jetbrains.com/help/resharper/2017.2/Using_Clang_Format.html
Vim
https://github.com/rhysd/vim-clang-format
https://github.com/chiel92/vim-autoformat
XCode: https://github.com/travisjeffery/ClangFormat-Xcode
C++ Standard Library (and other well-known C++ libraries like Boost) use these guidelines:
16
Style
Name function parameters with an t_ prefix. t_ can be thought of as "the", but the
meaning is arbitrary. The point is to distinguish function parameters from other variables in
scope while giving us a consistent naming strategy.
Any prefix or postfix can be chosen for your organization. This is just one example. This
suggestion is controversial, for a discussion about it see issue #11.
struct Size
{
int width;
int height;
private:
int m_width;
int m_height;
};
17
Style
http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-
in-a-c-identifier
Well-Formed Example
class MyClass
{
public:
MyClass(int t_data)
: m_data(t_data)
{
}
private:
int m_data;
};
Use nullptr
C++11 introduces nullptr which is a special value denoting a null pointer. This should be
used instead of 0 or NULL to indicate a null pointer.
Comments
Comment blocks should use // , not /* */ . Using // makes it much easier to comment
out a block of code while debugging.
18
Style
/*
// this function does something
int myFunc()
{
}
*/
Include Guards
Header files must contain a distinctly-named include guard to avoid problems with including
the same header multiple times and to prevent conflicts with headers from other projects.
#ifndef MYPROJECT_MYCLASS_HPP
#define MYPROJECT_MYCLASS_HPP
namespace MyProject {
class MyClass {
};
}
#endif
You may also consider using the #pragma once directive instead which is quasi-standard
across many compilers. It's short and makes the intent clear.
19
Style
// Bad Idea
// This compiles and does what you want, but can lead to confusing
// errors if modification are made in the future and close attention
// is not paid.
for (int i = 0; i < 15; ++i)
std::cout << i << std::endl;
// Bad Idea
// The cout is not part of the loop in this case even though it appears to be.
int sum = 0;
for (int i = 0; i < 15; ++i)
++sum;
std::cout << i << std::endl;
// Good Idea
// It's clear which statements are part of the loop (or if block, or whatever).
int sum = 0;
for (int i = 0; i < 15; ++i) {
++sum;
std::cout << i << std::endl;
}
// Good Idea
// Logical grouping, easier to read
if (x && y && myFunctionThatReturnsBool()
&& caseNumber3
&& (15 > 12 || 2 < 3)) {
}
Many projects and coding standards have a soft guideline that one should try to use less
than about 80 or 100 characters per line. Such code is generally easier to read. It also
makes it possible to have two separate files next to each other on one screen without having
a tiny font.
20
Style
// Worse Idea
// Requires potentially even more specific -I directives and
// makes code more difficult to package and distribute.
#include <string>
#include <MyHeader.hpp>
// Good Idea
// Requires no extra params and notifies the user that the file
// is a local file.
#include <string>
#include "MyHeader.hpp"
For POD types, the performance of an initializer list is the same as manual initialization, but
for other types there is a clear performance gain, see below.
// Bad Idea
class MyClass
{
public:
MyClass(int t_value)
{
m_value = t_value;
}
private:
int m_value;
};
// Bad Idea
// This leads to an additional constructor call for m_myOtherClass
// before the assignment.
class MyClass
{
public:
MyClass(MyOtherClass t_myOtherClass)
{
m_myOtherClass = t_myOtherClass;
21
Style
private:
MyOtherClass m_myOtherClass;
};
// Good Idea
// There is no performance gain here but the code is cleaner.
class MyClass
{
public:
MyClass(int t_value)
: m_value(t_value)
{
}
private:
int m_value;
};
// Good Idea
// The default constructor for m_myOtherClass is never called here, so
// there is a performance gain if MyOtherClass is not is_trivially_default_constructib
le.
class MyClass
{
public:
MyClass(MyOtherClass t_myOtherClass)
: m_myOtherClass(t_myOtherClass)
{
}
private:
MyOtherClass m_myOtherClass;
};
In C++11 you can assign default values to each member (using = or using {} ).
// ... //
private:
int m_value = 0; // allowed
unsigned m_value_2 = -1; // narrowing from signed to unsigned allowed
// ... //
22
Style
// Best Idea
// ... //
private:
int m_value{ 0 }; // allowed
unsigned m_value_2 { -1 }; // narrowing from signed to unsigned not allowed, leads t
o a compile time error
// ... //
Prefer {} initialization over = unless you have a strong reason not to.
Forgetting to initialize a member is a source of undefined behavior bugs which are often
extremely hard to find.
If the member variable is not expected to change after the initialization, then mark it const .
class MyClass
{
public:
MyClass(int t_value)
: m_value{t_value}
{
}
private:
const int m_value{0};
};
Since a const member variable cannot be assigned a new value, such a class may not have
a meaningful copy assignment operator.
23
Style
In general, using auto will avoid most of these issues, but not all.
Make sure you stick with the correct integer types and remain consistent with the C++
standard library. It might not warn on the platform you are currently using, but it probably will
when you change platforms.
Note that you can cause integer underflow when performing some operations on unsigned
values. For example:
std::vector<int> v1{2,3,4,5,6,7,8,9};
std::vector<int> v2{9,8,7,6,5,4,3,2,1};
const auto s1 = v1.size();
const auto s2 = v2.size();
const auto diff = s1 - s2; // diff underflows to a very large number
One particularly large project (OpenStudio) uses .hpp and .cpp for user-generated files and
.hxx and .cxx for tool-generated files. Both are well recognized and having the distinction is
helpful.
24
Style
The above code succeeds when making a debug build, but gets removed by the compiler
when making a release build, giving you different behavior between debug and release
builds. This is because assert() is a macro which expands to nothing in release mode.
However, you can easily create unreadable expressions using too much or wrong operator
overloading. When overloading operators, there are three basic rules to follow as described
on stackoverflow.
Overloading operator=() when handling resources is a must. See Consider the Rule of
Zero below.
For all other operators, only overload them when they are used in a context that is
commonly connected to these operators. Typical scenarios are concatenating things
with +, negating expressions that can be considered "true" or "false", etc.
Always be aware of the operator precedence and try to circumvent unintuitive
constructs.
Do not overload exotic operators such as ~ or % unless implementing a numeric type or
following a well recognized syntax in specific domain.
Never overload operator,() (the comma operator).
Use non-member functions operator>>() and operator<<() when dealing with
streams. For example, you can overload operator<<(std::ostream &, MyClass const &)
25
Style
More tips regarding the implementation details of your custom operators can be found here.
Conversion Operators
Similarly to single parameter constructors, conversion operators can be called by the
compiler and introduce unexpected overhead. They should also be marked as explicit .
//bad idea
struct S {
operator int() {
return 2;
}
};
//good idea
struct S {
explicit operator int() {
return 2;
}
};
26
Style
ownership.
The goal is to let the compiler provide optimal versions that are automatically maintained
when more member variables are added.
The original article provides the background, while a follow up article explains techniques for
implementing nearly 100% of the time.
27
Considering Safety
Considering Safety
optimize the code and helps the developer know if a function has a side effect. Also, using
const & prevents the compiler from copying data unnecessarily. The comments on const
// Bad Idea
class MyClass
{
public:
void do_something(int i);
void do_something(std::string str);
};
// Good Idea
class MyClass
{
public:
void do_something(const int i);
void do_something(const std::string &str);
};
references: https://github.com/lefticus/cppbestpractices/issues/21
https://twitter.com/lefticus/status/635943577328095232
28
Considering Safety
private:
int m_int_value;
}
Instead, pass and return simple types by value. If you plan not to change passed value,
declare them as const , but not const refs:
// Good Idea
class MyClass
{
public:
explicit MyClass(const int t_int_value)
: m_int_value(t_int_value)
{
}
private:
int m_int_value;
}
Why? Because passing and returning by reference leads to pointer operations instead by
much more faster passing values in processor registers.
29
Considering Safety
// Bad Idea
MyClass *myobj = new MyClass;
// ...
delete myobj;
// Good Idea
auto myobj = std::make_unique<MyClass>(constructor_param1, constructor_param2); // C++
14
auto myobj = std::unique_ptr<MyClass>(new MyClass(constructor_param1, constructor_para
m2)); // C++11
auto mybuffer = std::make_unique<char[]>(length); // C++14
auto mybuffer = std::unique_ptr<char[]>(new char[length]); // C++11
// ...
// myobj is automatically freed for you whenever it is no longer used.
Use Exceptions
Exceptions cannot be ignored. Return values, such as using boost::optional , can be
ignored and if not checked can cause crashes or memory errors. An exception, on the other
hand, can be caught and handled. Potentially all the way up the highest level of the
application with a log and automatic restart of the application.
Stroustrup, the original designer of C++, makes this point much better than I ever could.
30
Considering Safety
Use the C++-style cast (static_cast<>, dynamic_cast<> ...) instead of the C-style cast. The
C++-style cast allows more compiler checks and is considerably safer.
// Bad Idea
double x = getX();
int i = (int) x;
Additionally the C++ cast style is more visible and has the possibility to search for.
But consider refactoring of program logic (for example, additional checking on overflow and
underflow) if you need to cast double to int . Measure three times and cut
0.9999999999981 times.
It is technically possible to make typesafe C-style variadic functions with some compilers
Additional Resources
How to Prevent The Next Heartbleed by David Wheeler is a good analysis of the current
state of code safety and how to ensure safe code.
31
Considering Maintainability
Considering Maintainability
// Bad Idea
#define PI 3.14159;
// Good Idea
namespace my_project {
class Constants {
public:
// if the above macro would be expanded, then the following line would be:
// static const double 3.14159 = 3.14159;
// which leads to a compile-time error. Sometimes such errors are hard to understa
nd.
static constexpr double PI = 3.14159;
};
}
32
Considering Maintainability
// Bad Idea
assert(set_value(something));
// Better Idea
[[maybe_unused]] const auto success = set_value(something);
assert(success);
The assert() will be removed in release builds which will prevent the set_value call from
ever happening.
So while the second version is uglier, the first version is simply not correct.
33
Considering Portability
Considering Portability
http://www.viva64.com/en/a/0010/
std::thread
C++11's threading capabilities should be utilized over pthread or WinThreads .
Other Concerns
Most of the other concerns in this document ultimately come back to portability issues. Avoid
statics is particularly of note.
34
Considering Threadability
Considering Threadability
Statics
Besides being global data, statics are not always constructed and deconstructed as you
would expect. This is particularly true in cross-platform environments. See for example, this
g++ bug regarding the order of destruction of shared static data loaded from dynamic
modules.
Shared Pointers
std::shared_ptr is "as good as a global" (http://stackoverflow.com/a/18803611/29975)
because it allows multiple pieces of code to interact with the same data.
Singletons
A singleton is often implemented with a static and/or shared_ptr .
35
Considering Threadability
For more information see the following article from Herb Sutter:
http://herbsutter.com/2013/05/24/gotw-6a-const-correctness-part-1-3/
See also related safety discussion about const & return values
36
Considering Performance
Considering Performance
Build Time
Forward Declare When Possible
This:
instead of:
37
Considering Performance
After you build using Templight, you will need to analyze the results. The templight-tools
project provides various methods. (Author's Note: I suggest using the callgrind converter and
visualizing the results with kcachegrind).
include-what-you-use is a tool that can help you identify which headers you need.
38
Considering Performance
Precompiled headers is supported by the most common compiler, like GCC, Clang and
Visual Studio. Tools like cotire (a plugin for cmake) can help you to add precompiled headers
to your build system.
ccache
warp, Facebook's preprocessor
Runtime
Analyze the Code!
There's no real way to know where your bottlenecks are without analyzing the code.
http://developer.amd.com/tools-and-sdks/opencl-zone/codexl/
http://www.codersnotes.com/sleepy
// This
std::vector<ModelObject> mos{mo1, mo2};
// -or-
auto mos = std::vector<ModelObject>{mo1, mo2};
39
Considering Performance
// Don't do this
std::vector<ModelObject> mos;
mos.push_back(mo1);
mos.push_back(mo2);
Initializer lists are significantly more efficient; reducing object copies and resizing of
containers.
// Instead of
auto mo1 = getSomeModelObject();
auto mo2 = getAnotherModelObject();
doSomething(mo1, mo2);
// consider:
doSomething(getSomeModelObject(), getAnotherModelObject());
This sort of code prevents the compiler from performing a move operation...
Certain coding choices we make (such as declaring our own destructor or assignment
operator or copy constructor) prevents the compiler from generating a move constructor.
would suffice. However, MSVC2013 doesn't seem to like this code yet.
is because the reference count must be atomic and thread-safe. So this comment just re-
enforces the note above: avoid temporaries and too many copies of objects. Just because
we are using a pImpl it does not mean our copies are free.
40
Considering Performance
// Bad Idea
std::string somevalue;
if (caseA) {
somevalue = "Value A";
} else {
somevalue = "Value B";
}
// Better Idea
const std::string somevalue = caseA ? "Value A" : "Value B";
// Bad Idea
std::string somevalue;
if (caseA) {
somevalue = "Value A";
} else if(caseB) {
somevalue = "Value B";
} else {
somevalue = "Value C";
}
// Better Idea
const std::string somevalue = [&](){
if (caseA) {
return "Value A";
} else if (caseB) {
return "Value B";
} else {
return "Value C";
}
}();
41
Considering Performance
than stack allocations, but sometimes we have to use them. To make matters worse,
creating a shared_ptr actually requires 2 heap allocations.
std::shared_ptr<ModelObject_Impl>(new ModelObject_Impl());
// should become
std::make_shared<ModelObject_Impl>(); // (it's also more readable and concise)
std::make_unique<ModelObject_Impl>();
Current best practices suggest returning a unique_ptr from factory functions as well, then
converting the unique_ptr to a shared_ptr if necessary.
std::unique_ptr<ModelObject_Impl> factory();
42
Considering Performance
// Good Idea
for (int i = 0; i < 15; ++i)
{
MyObject obj(i);
// do something with obj
}
// Bad Idea
MyObject obj; // meaningless object initialization
for (int i = 0; i < 15; ++i)
{
obj = MyObject(i); // unnecessary assignment operation
// do something with obj
}
// obj is still taking up memory for no reason
double is the recomended default choice as it is the default type for floating point values in
C++.
// Bad Idea
for (int i = 0; i < 15; i++)
{
std::cout << i << '\n';
}
// Good Idea
for (int i = 0; i < 15; ++i)
{
std::cout << i << '\n';
}
43
Considering Performance
Even if many modern compilers will optimize these two loops to the same assembly code, it
is still good practice to prefer ++i . There is absolutely no reason not to and you can never
be certain that your code will not pass a compiler that does not optimize this. You should be
also aware that the compiler will not be able optimize this only for integer types and not
necessarily for all iterator or other user defined types.
The bottom line is that it is always easier and recommended to use the pre-increment
operator if it is semantically identical to the post-increment operator.
// Bad Idea
std::cout << someThing() << "\n";
// Good Idea
std::cout << someThing() << '\n';
This is very minor, but a "\n" has to be parsed by the compiler as a const char * which
has to do a range check for \0 when writing it to the stream (or appending to a string). A
'\n' is known to be a single character and avoids many CPU instructions.
If used inefficiently very many times it might have an impact on your performance, but more
importantly thinking about these two usage cases gets you thinking more about what the
compiler and runtime has to do to execute your code.
// Bad Idea
auto f = std::bind(&my_function, "hello", std::placeholders::_1);
f("world");
// Good Idea
auto f = [](const std::string &s) { return my_function("hello", s); };
f("world");
44
Considering Performance
Be aware of how to use in_place_t and related tags for effecient creation of objects such
as std::tuple , std::any and std::variant .
45
Enable Scripting
Enable Scripting
The combination of scripting and compiled languages is very powerful. It gives us the things
we've come to love about compiled languages: type safety, performance, thread safety
options, consistent memory model while also giving us the flexibility to try something new
quickly without a full rebuild.
The VM based compiled languages have learned this already: JRuby, Jython, IronRuby,
IronPython
ChaiScript
AngelScript
luabind
sol2 (bindings for Lua)
SWIG (simplified wrapper and interface generator)
pybind11 (Python and modern C++ interoperability)
46
Further Reading
Further Reading
Note: This book has now inspired a video series from O'Reilly, Learning C++ Best Practices
C++
https://github.com/isocpp/CppCoreGuidelines The C++ Core Guidelines are a set of
tried-and-true guidelines, rules, and best practices about coding in C++
https://www.gitbook.com/book/alexastva/the-ultimate-question-of-programming-
refactoring-/details - The Ultimate Question of Programming, Refactoring, and
Everything
http://llvm.org/docs/CodingStandards.html - LLVM Coding Standards - very well written
http://geosoft.no/development/cppstyle.html
https://google.github.io/styleguide/cppguide.html (Note that Google's standard
document makes several recommendations which we will NOT be following. For
example, they explicitly forbid the use of exceptions, which makes RAII impossible.)
https://isocpp.org/faq/
http://www.cplusplus.com/
http://www.gamasutra.com/view/news/128836/InDepth_Static_Code_Analysis.php -
Article from John Carmack on the advantages of static analysis
https://svn.boost.org/trac/boost/wiki/BestPracticeHandbook - Best Practice Handbook
from Nial Douglas
http://sourceforge.net/apps/mediawiki/cppcheck/index.php?title=ListOfChecks
http://emptycrate.com/
http://stackoverflow.com/questions/tagged/c%2b%2b-faq?sort=votes&pageSize=15 -
StackOverflow C++ FAQ
http://codergears.com/qacenter/ discussion center for C and C++ best practices
http://www.viva64.com/en/b/0391/ The Ultimate Question of Programming, Refactoring,
and Everything
CMake
https://cmake.org/cmake/help/latest/manual/cmake.1.html - Be aware that there are --
warn command line options for CMake that can catch some issues.
https://github.com/Akagi201/learning-cmake
https://codingnest.com/basic-cmake/
https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1 - Effective CMake
47
Further Reading
online book
https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/
48
Final Thoughts
Final Thoughts
Expand your horizons and use other programming languages. Other languages have
different constructs and expressions. Learning what else is out there will encourage you to
be more creative with your C++ and write cleaner, more expressive code.
49