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:
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."
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 *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.
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 😉)
Retweet the main parent tweet to get the tread going!
I'll try to provide a link for each Best Practice.
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};
```
```
static constexpr std::array<int, 5> angles{-90,-45,0,45,90};
```
The difference is beyond huge.
godbolt.org/z/rQQnch
See also:
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
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.
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.
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.
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.
```
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.
Aside: `enum struct` and `enum class` are equivalent. Logically `enum struct` makes more sense, since they are public names. Which do you prefer?
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.
```
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
}
```
```
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
}
```
@copperspice_cpp
Ok, there's a lot that's UB and it's hard to keep track off, so we'll give some examples.
```
int Class::member() {
if (this == nullptr) {
// removed by the compiler, it would be UB
return 42;
} else {
return 0;
}
}
```
```
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.
Address sanitizer, UB Sanitizer, Thread sanitizer can find many issues almost like magic.
@johnregehr recommends always enabling asan and ubsan during development.
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…
cppcheck and clang-tidy are free and have integration with every IDE and editor today.
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.
Support *at least* 2 compilers on your platform. Each compiler does different analysis.
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.
* 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.
* vcpkg
* conan @conan_io
* hunter
* etc
Also help with maintaining portability and reducing load on the developers.
Videos here:
First off, I'm not an Almost Always Auto person, but let me ask you this:
What is the type of `std::count`?
```
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.
This will result in better error messages (eventually) and better compile times than SFINAE. Besides much more readable code than 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
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.
Same arguments as #23. This is a do-not-repeat-yourself principle for exception handling routines. Check out the video for more details.
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.
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).
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."
This is a high level concept, specific ideas will follow.
Arthur and @StephanTLavavej make the argument quite well.
reddit.com/r/cpp/comments…
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.
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.
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.
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)
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
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/
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…
Over 200 weeks straight now!! Plus I'm in the middle of a free class!
youtube.com/playlist?list=…
If you want more info, bring me into your company for training.
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]]
If you're currently looking to finally move to "modern" C++ please skip C++11.
Have I forgotten anything major?
```
std::vector<shared_ptr<int>> vec{std::make_shared<int>(40), std::make_shared<int>(2)};
```
Does. Then...
```
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.