, 63 tweets, 16 min read
My Authors
Read all threads
For 2020 I'm doing a 1 C++ Best Practice per retweet thread!

I wrote my first blog article about C++ best practices in 2007. In many ways I've been repeating myself over and over again for the last 13ish years.

And very little of what I say is novel.

Thread follows:
Even though I've been repeating many of the same best practices for a while, I think my style is relatively unique.

One client told me "I like how you don't just give us a rule, you tell us why. <other guy> never gives us reasons."
My goal as a trainer and a contractor (seems to be) to work myself out of a job.

I want everyone to:

1. Learn how to experiment for themselves
2. Not just believe me, but test it
3. Learn how the language works
4. Stop making the same mistakes of the last generation
I'm thinking about changing my title from "C++ Trainer" to "C++ Guide."

I *always* adapt my courses and material to the class I currently have. We might agree on X but I change it to Y half way through the first day to meet the needs of the organization.
Along the way we experiment and learn as a group.

I often learn something also. Every group is unique, every class has new questions.

But a lot of the questions are still the same ones over and over (to the point where I get to look like a mind reader, that bit's fun 😉)
So hence this thread (to spread the word on the long standing best practices).

Retweet the main parent tweet to get the tread going!

I'll try to provide a link for each Best Practice.
1: `constexpr` everything known at compile time.

Gone are the days of #define

People over complicate `constexpr`, so let's break down the simplest thing.

If you see something like (I've seen in real code):

```
static const std::vector<int> angles{-90,-45,0,45,90};
```
This really needs to be:

```
static constexpr std::array<int, 5> angles{-90,-45,0,45,90};
```

The difference is beyond huge.

godbolt.org/z/rQQnch

See also:
2: `const` Everything that's not `constexpr`

Many people (like @gregcons and @JamesMcNellis ) have said this many times.

This does 2 things:

1. It forces us to think about the initialization and lifetime of objects, which affects performance
2. Communicates meaning to the readers of our code.

And as an aside, if it's a static object the compiler is now free to move it into the constants portion of the binary, which can affect the optimizer as well.
3: Follow the Rule of 0

en.cppreference.com/w/cpp/language…

No destructor is always better. Gives the compiler much more room for optimization.

See also:

`std::unique_ptr` can help with this, by providing a custom deleter.
4: If you must do manual resource management, follow the rule of 5

If you provide a destructor because `std::unique_ptr` doesn't make sense for your use case, you *must*

`delete` `=default` or implement the other special member functions.
Homework assignment: implement your own `unique_ptr`.

It's hard to get it 100% right. Write tests. Understand why the defaulted special member functions don't work.

Bonus points: implement it with C++20's `constexpr` dynamic allocation support.
5: prefer scoped enums

```
enum Choices;
enum OtherChoices
```

These two can easily get missed up and introduce things in the global namespace

```
enum class Choices
enum class OtherChoices
```

cannot get mixed up without much effort.
@ciura_victor has given interesting stories about this in his CppCon talks about @ClangPowerTools

Aside: `enum struct` and `enum class` are equivalent. Logically `enum struct` makes more sense, since they are public names. Which do you prefer?
@ciura_victor @ClangPowerTools 6. Prefer algorithms over raw loops

Algorithms communicate meaning and helps us apply the "`const` all the things" rule.

In C++20 we get ranges, which make algorithms easier to use.
7. Prefer ranged-for loop syntax over old loops

```
for(int i = 0; i < container.size; ++i) {
// oops mismatched types
}
```

```
for (auto itr = container.begin();
itr != container2.end();
++itr) {
// oops, most of us have done this at some point
}
```
```
for(const auto &element : container) {
// eliminates both other problems
}
```
8. Use `auto` in ranged for loops

```
for (const int value : container_of_double) {
// accidental conversion, possible warning
}
```

```
for (const base value : container_of_derived) {
// accidental silent slicing
}
```
```
for (const auto &value : container) {
// no possible accidental conversion
}
```
9. Don't Invoke Undefined Behavior

@copperspice_cpp



Ok, there's a lot that's UB and it's hard to keep track off, so we'll give some examples.
10. Never test for `this` to be `nullptr`, it's UB

```
int Class::member() {
if (this == nullptr) {
// removed by the compiler, it would be UB
return 42;
} else {
return 0;
}
}
```
People used to do this all the time, but it's always been UB. You cannot access an object outside its lifetime
11. Never test for a `&` to be `nullptr`, it's UB

```
int get_value(int &thing) {
if (&thing == nullptr) {
// removed by compiler
return 42;
} else {
return thing;
}
}
```

It's UB to make a null reference, don't try it. Always assume a `&` is a value object.
"valid" not "value" object
12. Use the tools: sanitizers

Address sanitizer, UB Sanitizer, Thread sanitizer can find many issues almost like magic.

@johnregehr recommends always enabling asan and ubsan during development.
13: Use the tools: compiler warnings

You have many many warnings you are not using, most of them beneficial.

-Wall is *not* all
-Wextra is still barely scratching the surface!

github.com/lefticus/cppbe…
14: Use the tools: static analysis

cppcheck and clang-tidy are free and have integration with every IDE and editor today.
15: Use the tools: continuous builds

If your project is not automatically tested regularly you will not be able to maintain quality.

If you don't require 100% tests passing you will never know what state the code is in.
16. Use the tools: multiple compilers

Support *at least* 2 compilers on your platform. Each compiler does different analysis.
17. Use the tools: automated tests

You need a single command to run tests. If you don't have that your tests will not be run.

* catch2
* ctest
* doctest
* gtest
* boost::test

You need to be familiar with these tools, what they do and pick from them.
18. Use the tools: build generators

* cmake
* meson
* etc

Raw make files or visual studio project files make each of the things listed above too difficult to implement.

Use a build tool to help you with maintaining portability across platforms and compilers.
19: Use the tools: Package managers

* vcpkg
* conan @conan_io
* hunter
* etc

Also help with maintaining portability and reducing load on the developers.

Videos here:
@conan_io 20: Prefer `auto` in many cases.

First off, I'm not an Almost Always Auto person, but let me ask you this:

What is the type of `std::count`?
@conan_io My answer is "I don't care."

```
const auto result = std::count( /* stuff */ );
```

avoids unnecessary conversions and data loss.

Same as ranged-for loops.

Also, `auto` requires initialization, same as `const`, same reasoning for why that's good.
@conan_io 21: Constrain your template parameters with concepts (C++20)

This will result in better error messages (eventually) and better compile times than SFINAE. Besides much more readable code than SFINAE.

22: Prefer `if constexpr` over SFINAE

See #21. SFINAE is kind of write-only code. `if constexpr` doesn't have all the same flexibility, but use it when you can.

See Practical C++17
23: De-template-ize your generic code

Move things outside of your templates when you can. Use other functions. Use base classes.

The compiler is still free to inline them, or leave them out of line.
This will improve compile times and reduce binary sizes. Both are helpful. It also eliminates the thing that people think of as "template code bloat" (which IMO doesn't actually exist (articles.emptycrate.com/2008/05/06/nob…) (article formatting got broken at some point, sorry)
24: Use Lippincott Functions

Same arguments as #23. This is a do-not-repeat-yourself principle for exception handling routines. Check out the video for more details.

25: Hire a Trainer To Come Onsite For Your Company

This gets your team thinking in a new direction, improves morale and boosts employee retention.

Since you made it this far, I'm going to offer you a coupon.
Three requirements:

1) contact me articles.emptycrate.com/contact.html
2) mention this thread
3) promise you'll have the students read this thread

You'll get 10% off onsite training costs at your company from me. (travel costs not discounted).
26: Don't Be Afraid Of Templates

This is the ultimate DRY principle in C++. Templates can be complex, daunting and Turing complete, but they don't have to be.

15 years ago it seemed the prevailing attitude is "templates aren't for normal people."
Fortunately this is less true today. And we have more tools today, concepts, generic lambdas, etc.
27: Make your interfaces hard to use wrong.

This is a high level concept, specific ideas will follow.
28: Consider if using the API wrong invokes undefined behavior or leaks resources.
29: Use [[nodiscard]] liberally

Arthur and @StephanTLavavej make the argument quite well.

reddit.com/r/cpp/comments…
30: Don't return raw pointers.

Makes the reader of the code and user of the library think too hard about ownership semantics.

Prefer a reference, smart pointer, non owning pointer wrapper or consider an optional reference.
31: Prefer stack over heap.

Stack objects (locally scoped objects that are not dynamically allocated) are much more optimizer friendly, cache friendly, and may be eliminated altogether.

As @bjorn_fahller said, assume any pointer indirection is a cache miss.
32: Consider computer architecture

While you are technically writing code for the C++ abstract machine (see from @PatriceRoy1) your program will ultimately run on a real computer.

Be aware that there is a cache and pointer indirection has a cost.
33: Know your containers.

Prefer them in this order:

array
vector

Anything else needs a comment and justification for why.

A flat map with linear search is likely better then a std::map for small containers. (but if you need key lookup, use map and evaluate)
34: avoid std::bind

Again, @StephanTLavavej has done a great job covering this. It's slow and crazy complicated.

C++14 lambdas do a better job in all cases.

See also
Homework assignment: write an explanation of why std::array could be considered a "negative cost abstraction" with @CompileExplore examples and send the answers to @lennymaiorani 🤣
35: Attend a conference or local user group

There is almost certainly one near you.

It's a great way to network and learn new things.

isocpp.org/wiki/faq/confe…

meetingcpp.com/usergroups/
36: Know cppreference.com

The website is awesome, but you might not know that you can create an account and customize the content to the version of C++ you are using.

Also, you can execute examples and download an offline version!

en.cppreference.com/w/Cppreference…
37: Subscribe to C++ Weekly

Over 200 weeks straight now!! Plus I'm in the middle of a free class!

youtube.com/playlist?list=…
38: Understand what std::make_shared does, why to prefer it (when you need shared ownership semantics) and under what special circumstances to avoid it.

If you want more info, bring me into your company for training.
39: No more `new`!

You're already avoiding the heap and using smart pointers for resource management, right?!

"But I use Qt!" you say?

Have you ever thought about writing your own make_qobject helper?

Give it the semantics you need and be sure to use [[nodiscard]]
40: Skip C++11

If you're currently looking to finally move to "modern" C++ please skip C++11.

I think I'm a bit done.

Have I forgotten anything major?
41: Don't use initializer_list for non trivial types.

Homework assignment: use @Andreas__Fertig 's awesome cppinsights.io to understand exactly what

```
std::vector<shared_ptr<int>> vec{std::make_shared<int>(40), std::make_shared<int>(2)};
```

Does. Then...
Compare that to

```
std::array<std::shared_ptr<int>, 2> data{std:make_shared<int>(40), std::make_shared(2)};
```

And explain the difference.

If you can do this you understand more than most C++ developers.
Missing some Tweet in this thread? You can try to force a refresh.

Enjoying this thread?

Keep Current with Jason Turner

Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Follow Us on Twitter!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just three indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3.00/month or $30.00/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!