C++¶
Talks & Articles¶
signed
vs unsigned int
’s — performance-wise¶
Non-constant constant-exspressions in C++¶
When fences and weak ordering needed¶
CppCon 2016: Hans Boehm “Using weakly ordered C++ atomics correctly”
C++17 overview¶
C++ and exceptions¶
Embracing (and also Destroying) Variant Types Safely - Andrei Alexandrescu - CppCon 2021¶
- Usage of parameter packs (
head
,tail
,type_sequence
).
clang libAST¶
An example of parsing C++ code.
Unwinding a Stack by Hand with Frame Pointers and ORC¶
A great article about stack unwinding.
Getting symbol names by their addresses:
addr2line -e /usr/lib/debug/lib/modules/5.4.17-2136.304.4.1.el8uek.x86_64/vmlinux \
-j .text -ipfas \
0x30d178 0x2fb393 0x2fc1c9 0x2fd20d 0x301a80 0x301c3e 0x2f4437 0x2f55e4 0x2f5644 0x44f0 0xa001b8
0x000000000030d178: read_word_at_a_time at compiler.h:350
(inlined by) dentry_string_cmp at dcache.c:252
(inlined by) dentry_cmp at dcache.c:406
(inlined by) __d_lookup_rcu at dcache.c:2672
0x00000000002fb393: lookup_fast at namei.c:1659
...
Early return patter¶
Total functions vs Partial functions¶
Let’s say instead of having a precondition that the range is non-empty, I want to handle that case too. I want to write a total function instead of a partial function. The best way to do that is either return some value (if I can) or no value (if I can’t). That’s an optional, that’s what it’s for: to handle returning something or nothing.
SIMD in C++20¶
Standard library algorithms implementation using SIMD.
Generate tuple consisting of N types¶
Ranges¶
Projection — the 3rd argument¶
Example 1:
Example 2, case insensitive search
Views compositon — |
:¶
temporary ranges & std::ranges::dangling
¶
There is a special type that is returned when a function is called with a temporary value:
will result in:
<source>:22:48: error: base operand of '->' has non-pointer type 'std::ranges::dangling'
22 | std::cout << "std::ranges::find_if: " << it->name_ << '\n';
|
Erase–remove idiom¶
Concepts¶
Purpose:
- Better error messages.
- Faster compilation checks can be performed before instantiating and parsing a template function/class.
- Glorified
std::enable_if_t
.
Constraining return type¶
Use arrow: ->
:
template<class T>
concept BoolComparable = requires(T lhs, T rhs) {
{lhs == rhs} -> std::convertible_to<bool>;
{ shape.draw() }; -> std::same_as<void>;
};
Constraints for multiple types¶
Consider heterogeneous comparison ("asdf" == std::string("asdf")
):
template<class T, class U>
concept EqualityCompare = requires(T op_T, U op_U) {
{op_T == op_U} -> std::convertible_to<bool>;
{op_U == op_T} -> std::convertible_to<bool>;
};
template<class T, EqualityCompare<T> U>
bool are_equal(const T op_T, const U op_U) {...}
template<class T, class U> requires EqualityCompare<T, U>
bool are_equal(const T op_T, const U op_U) {...}
bool are_equal(const auto op_T, const auto op_U)
requires EqualityCompare<declytpe(op_T), decltype(op_U)> {...}
Using concepts as parameters¶
void drawShape(const ShapeConcept auto &shape) {
shape.draw();
}
template<class ShapeConcept>
void drawShape(const ShapeConcept &shape) {
shape.draw();
}
Using concepts as return types¶
Reference collapsing rules¶
A good explanation of rvalues.
Geven:
There is a special template argument deduction rule for function templates that take an argument by rvalue reference to a template argument:
- When
foo()
is called on an lvalue of typeA
, thenT
resolves toA&
and hence, by the reference collapsing rules above, the argument type effectively becomesA&
. - When
foo()
is called on an rvalue of typeA
, thenT
resolves toA
, and hence the argument type becomesA&&
.
auto vs decltype¶
Src.
rvalue
is an xvalue
if it is one of the following:
- A function call where the function’s return value is declared as an
rvalue
reference. An example would bestd::move(x)
. - A cast to an
rvalue
reference. An example would bestatic_cast<A&&>(a)
. - A member access of an
xvalue
. Example:(static_cast<A&&>(a)).m_x
.
All other rvalues
are prvalues
. We are now in a position to describe how decltype
deduces
the type of a complex expression.
Let expr
be an expression that is not a plain, unparenthesized variable, function parameter, or
class member access. Let T
be the type of expr
.
- If
expr
is anlvalue
, thendecltype(expr)
isT&
. - If
expr
is anxvalue
, thendecltype(expr)
isT&&
. - Otherwise,
expr
is aprvalue
, anddecltype(expr)
isT
.
RVO / URVO / NRVO¶
Consider the following:
The output is just
As you can see there is no things like: (1) construct temporary string in the function Meow
,
(2) construct string s
in main
, (3) call s.operator=
, (4) destruct temporary string (constructed in Meow
).
Instead of it just a single ctor called.
Lesser known C++ std algorithms¶
Uncategorised¶
Non-modifying sequence operations¶
Modifying sequence operations¶
Sort related¶
merge
,inplace_merge
partial_sort
nth_element
partition
,stable_partition
is_partitioned
,partition_point
Minimum/maximum¶
C++ Core Guidelines excerpts¶
C.138: Create an overload set for a derived class and its bases with using
¶
Reason¶
Without a using
declaration, member functions in the derived class hide the entire inherited overload sets.
Example, good | |
---|---|
Note¶
This issue affects both virtual and non-virtual member functions
For variadic bases, C++17 introduced a variadic form of the using-declaration,
C.165: Use using
for customization points¶
Reason¶
To find function objects and functions defined in a separate namespace to “customize” a common function.
Example¶
Consider swap
. It is a general (standard-library) function with a definition that will work for just about
any type. However, it is desirable to define specific swap()
’s for specific types. For example, the general
swap()
will copy the elements of two vectors being swapped, whereas a good specific implementation will not
copy elements at all.
bad | |
---|---|
The std::swap()
in f1()
does exactly what we asked it to do: it calls the swap()
in namespace std
.
Unfortunately, that’s probably not what we wanted. How do we get N::X
considered?
But that might not be what we wanted for generic code. There, we typically want the specific function if it exists and the general function if not. This is done by including the general function in the lookup for the function:
good | |
---|---|
C.129: When designing a class hierarchy, distinguish between implementation inheritance and interface inheritance¶
Reason¶
Implementation details in an interface make the interface brittle; that is, make its users vulnerable to having to recompile after changes in the implementation. Data in a base class increases the complexity of implementing the base and can lead to replication of code.
Note¶
Interface inheritance
:-
is the use of inheritance to separate users from implementations, in particular to allow derived classes to be added and changed without affecting the users of base classes.
Implementation inheritance
:- is the use of inheritance to simplify implementation of new facilities by making useful operations available for implementers of related new operations (sometimes called “programming by difference”).
A pure interface class is simply a set of pure virtual functions; see I.25.
In early OOP (e.g., in the 1980s and 1990s), implementation inheritance and interface inheritance were often mixed and bad habits die hard. Even now, mixtures are not uncommon in old code bases and in old-style teaching material.
The importance of keeping the two kinds of inheritance increases
- with the size of a hierarchy (e.g., dozens of derived classes),
- with the length of time the hierarchy is used (e.g., decades), and
- with the number of distinct organizations in which a hierarchy is used (e.g., it can be difficult to distribute an update to a base class)
Problems:
- As the hierarchy grows and more data is added to Shape, the constructors get harder to write and maintain.
- Why calculate the center for the Triangle? we might never use it.
- Add a data member to Shape (e.g., drawing style or canvas) and all classes derived from Shape and all code using Shape will need to be reviewed, possibly changed, and probably recompiled.
The implementation of Shape::move()
is an example of implementation inheritance: we have defined
move()
once and for all for all derived classes. The more code there is in such base class member function
implementations and the more data is shared by placing it in the base, the more benefits we gain — and the
less stable the hierarchy is.
This Shape
hierarchy can be rewritten using interface inheritance:
Note that a pure interface rarely has constructors: there is nothing to construct.
The interface is now less brittle, but there is more work in implementing the member functions. For example, center has to be implemented by every class derived from Shape.
Dual hierarchy¶
How can we gain the benefit of stable hierarchies from implementation hierarchies and the benefit of implementation reuse from implementation inheritance? One popular technique is dual hierarchies. There are many ways of implementing the idea of dual hierarchies; here, we use a multiple-inheritance variant.
First we devise a hierarchy of interface classes:
To make this interface useful, we must provide its implementation classes (here, named equivalently, but
in the Impl
namespace):
Now Shape is a poor example of a class with an implementation, but bear with us because this is just a simple example of a technique aimed at more complex hierarchies.
And we could extend the hierarchies by adding a Smiley
class ( :-)
):
There are now two hierarchies:
- interface:
Smiley
->Circle
->Shape
- implementation:
Impl::Smiley
->Impl::Circle
->Impl::Shape
Since each implementation is derived from its interface as well as its implementation base class we get a lattice (DAG):
As mentioned, this is just one way to construct a dual hierarchy.
The implementation hierarchy can be used directly, rather than through the abstract interface.
This can be useful when the implementation class has members that are not offered in the abstract interface or if direct use of a member offers optimization opportunities (e.g., if an implementation member function is final).
Note¶
Another (related) technique for separating interface and implementation is Pimpl.
Note¶
There is often a choice between offering common functionality as (implemented) base class functions and free-standing functions (in an implementation namespace). Base classes gives a shorter notation and easier access to shared data (in the base) at the cost of the functionality being available only to users of the hierarchy.
R.37: Do not pass a pointer or reference obtained from an aliased smart pointer¶
Reason¶
Violating this rule is the number one cause of losing reference counts and finding yourself with a dangling pointer. Functions should prefer to pass raw pointers and references down call chains. At the top of the call tree where you obtain the raw pointer or reference from a smart pointer that keeps the object alive. You need to be sure that the smart pointer cannot inadvertently be reset or reassigned from within the call tree below.
Note¶
To do this, sometimes you need to take a local copy of a smart pointer, which firmly keeps the object alive for the duration of the function and the call tree.
Example¶
Consider this code:
The following should not pass code review:
The fix is simple — take a local copy of the pointer to “keep a ref count” for your call tree:
ES.50: Don’t cast away const¶
Reason¶
It makes a lie out of const
. If the variable is actually declared const
, modifying it results in undefined behavior.
Example, bad | |
---|---|
Example¶
Sometimes, you might be tempted to resort to const_cast
to avoid code duplication, such as when two accessor functions
that differ only in const-ness have similar implementations. For example:
Instead, prefer to share implementations. Normally, you can just have the non-const function call the const function. However, when there is complex logic this can lead to the following pattern that still resorts to a const_cast:
Although this pattern is safe when applied correctly, because the caller must have had a non-const object to begin with, it’s not ideal because the safety is hard to enforce automatically as a checker rule.
Instead, prefer to put the common code in a common helper function — and make it a template so that it deduces const
.
This doesn’t use any const_cast
at all:
Note: Don’t do large non-dependent work inside a template, which leads to code bloat. For example, a further improvement
would be if all or part of get_bar_impl
can be non-dependent and factored out into a common non-template function,
for a potentially big reduction in code size.
Exception¶
You might need to cast away const
when calling const-incorrect functions. Prefer to wrap such functions in inline
const-correct wrappers to encapsulate the cast in one place.
Example¶
Sometimes, “cast away const” is to allow the updating of some transient information of an otherwise immutable object.
Examples are caching, memoization, and precomputation. Such examples are often handled as well or better using mutable
or an indirection than with a const_cast
.
Consider keeping previously computed results around for a costly operation:
Here, get_val()
is logically constant, so we would like to make it a const
member. To do this we still need to mutate cache,
so people sometimes resort to a const_cast
:
Fortunately, there is a better solution: State that cache is mutable even for a const object:
An alternative solution would be to store a pointer to the cache:
That solution is the most flexible, but requires explicit construction and destruction of *cache
(most likely in the constructor and destructor of X).
In any variant, we must guard against data races on the cache in multi-threaded code, possibly using a std::mutex
.