TIL-2: C++ Best Practices, Initializer List, Extern Template
Compiler Warnings
- Something I don’t know about:
Wpedantic
disables language extension- which is good
Range-based for loop
-
const auto &
in most cases -
auto &
if you need to modify the element inside the container -
auto &&
if moving elements out of the container
Template
- Don’t use
Type T
for template. Use some meaningful names (likeBinaryPred
,UnaryPred
)
Inheritance
- In most cases, delete 4 special members (except constructor, destructor) when you use inheritance because it’s hard to define a correct copy/move constructor and assignment
- If there is no slicing issue, you could try default them
Undefined Behavior
- Compilers are allowed to assume no undefined behavior happens in your code
- So, sometimes, your code will be optimized out because of this reason
- In case this happens, it really means you are relying on UB
- Checking
reference == nullptr
is undefined behavior - Dereferencing nullptr is also undefined behavior
switch
- Don’t use
default
inswitch
- We are talking about using switch with enum
- This rule applies to C
- Because if we add new options, default will disable us to get warning about the missing cases
const everywhere
-
const
as much as you can as soon as it doesn’t affect implicit move -
If you need to modify code, you can use immediately-invoked lambda 1
const auto data = [] () { std::vector<int> result; // modification here // NRVO (very likely) return result; }();
Exception Handling 2
- Lippincott exception handling
- A centralized way to deal with exception
- Basically, catch all the exception with
catch(...)
- Inside the catch block, call
lippincott
- Inside the catch block, call
- This
lippincott
function tries to rethrow the error withthrow;
inside a try block with many possible catches- If any of them matches, we process that particular exception
- If this function is called without any exception,
std::terminate
is called
Make your interface hard to use incorrectly
Effective C++ item 18: Make interfaces easy to use correctly and hard to use incorrectly
- Even though your data might be easy (just a int or bool or …), it’s still necessary for you to create a wrapper for it to tell people explicitly what’s the meaning of the code.
- Then, it’s hard for people to use your interface incorrectly
- Any function or overload can be
=deleted
in C++11- This disables people pass parameters that could implicitly convert to our specified types unexpectedly. For example, if we want to accept double and reject float, we delete the overload for float.
Don’t Use initializer_list For Non-Trivial Types
Talk: C++Now 2018: Jason Turner “Initializer Lists Are Broken, Let’s Fix Them”
-
std::initializer_lists<T>
is like a view instead of a concrete container that actually controls the lifetime of the object- It’s a pointer to an local array
- “An object of type
std::initializer_list<E>
is constructed from an initializer list as if the implementation generated and materialized a prvalue of type “array of N const E”, where N is the number of elements in the initializer list. Each element of that array is copy initialized with the corresponding element of the initializer list, and thestd::initializer_list<E>
object is constructed to refer to that array” - So, returning an initializer_list from a function is like returning a pointer to a local object
- Because the generated array is const, we can only use copy constructor when using initializer_list
- This causes us fail to construct a vector of unique_ptr via initializer_list
-
But, we can still construct an array via initializer list because this is aggregate initialization instead of initializer_list
struct X { X(std::initializer_list<double> v); }; X x{ 1,2,3 }; // is equivalent to const double __a[3] = {double{1}, double{2}, double{3}}; X x(std::initializer_list<double>(__a, __a+3));
- No narrowing conversions allowed because we are initializing the member via
{}
- Possible Improvements
- A non-const array with movable iterator
- check
std::make_move_iterator
- check
- Construct in-place by inheriting from vector
- A non-const array with movable iterator
- Interesting Observations
- Compilers may not have the ability to track the size of
const char *
. To have a higher probability of doing SSO, construct the string directly when trying to returnconst char *
from functions and later try to construct a string with thisconst char *
- Compilers may not have the ability to track the size of
-
std::in_place_t
- A tag used by
variant
,optional
andany
so that we can default construct the actual object in-place. These types could be non-movable/non-copyable. -
std::make_optional
,std::make_any
,std::in_place
(the global value that has typestd::in_place_t
),std::in_place_type<T>
(useful when trying to decide which type in variant to init),std::in_place_index<val>
(specify which index to create/set) - See this article for more details
- A tag used by
Extern Template 3
- If we use one template with a specific type frequently, extern template can help us solve the time to instantiate the template in each translational unit
- In the normal process, template is initialized in every translational unit if it’s used and during link time, only one instantiation will be kept.
- By using
extern template void Fun<T>();
, we signify that we have a template already instantiated.- Put this declaration in header file to make sure when we compile this code with
-O3
, the instantiated template is not inlined.
- Put this declaration in header file to make sure when we compile this code with
-
You can check Jason Turner’s channel C++ Weekly - Ep 70 - C++ IIFE in quick-bench.com for more information about IIFE (immediately-invoked function expression) ↩
-
Using a Lippincott Function for Centralized Exception Handling ↩