Also refer to:
1. EBO(Empty base class optimizations)
Put interface into base type, which does not have data members.
Every derived types use the same interface without cost.(i.e extra memory alignment, iff not virtual)
- unique_ptr and shared_ptr have a deleter:
std::default_delete by default.
(ref: http://vsdmars.blogspot.com/2014/12/c14-uniqueptr-with-type-erasure-as.html) - set and map, use std::less by default
(pure function/no state) - All containers(except array) have an allocator:
std::allocator
(ref:
http://vsdmars.blogspot.com/2017/10/c-allocator-template-code.html)
There are some interesting/useful attribute in C++20:
- likely
- unlikely
- no_unique_address
std::is_empty remember that bit fields of length 0 actually used to separate data member memory location.
(Ref:
Definition of empty type:
a non-union class type with no non-static data members other than bit-fields of size 0, no virtual functions, no virtual base classes, and no non-empty base classes.
Mark [no_unique_address] if the data member has the empty type.
Why is marking [no_unique_address] important?
Consider about unique_ptr's deleter, it's a stateless type, but in order to save it, it takes up 1 byte, but it has no state!
Thus, mark it as [no_unique_address], if needed, type is just another namespace to access the member function.
(Ref:
2. Constrining greedy templates
i.e Don't be over generic
(Book: C++ template complete guide, ch.7.6)
Put a SFINAE constraint on deduced function/member function.
(Ref:
std::remove_cvref (C++20) instead of using std::decay
For libstd++/libc++, were not using the latest feature due to backward compatibility.
3. Using common_type_t<T> as an identity meta-function
std::common_typecommon_type_t is useful to create a non-deduced context.
(or use type_identity in C++20)
e.g
i.e T is long and int!
fix:
or...
Above means that the second argument we do NOT want to deduce, but using our type explicitly.
e.g
template<typename T>
void frob(std::vector<T>& a, T b){
for (auto& c: a) {
c *= b;
}
}
std::vector<long> a{1,2,3};
frob(a, 5);
//error: no matching function for call to
//frob(std::vector<long int>&, int)
i.e T is long and int!
fix:
template<typename T>
void frob(std::vector<T>& a, typename type_identity<T>::type b);
or...
template<typename T>
void frob(std::vector<T>& a, std::common_type_t<T> b);
Above means that the second argument we do NOT want to deduce, but using our type explicitly.
4. Conditionally deleted special members
- When having generic wrapper like std::optional
we would want the wrapper type to model the same interface as the object it contains. - If is_copy_constructible<T> is false then we want is_copy_constructible<optional<T>> to be false.
- if is_default_constructible<T> is false then is_default_constructible<optional<T>> is false as well.
templatenize the member function and SFINAE.
(Ref: http://vsdmars.blogspot.com/2017/09/c-write-our-own-type-trait.html )
If finding compiler is fighting us over, using default/delete then.
Again, how?
Inheritance :-)
Move problem to base type.
template<typename T>
struct optional : maybe_copyable<is_copy_constructible_t<T>>
{
optional(const optional&) = default;
};
template<bool IsCopyable>
struct maybe_copyable{};
// SMART...
template<>
struct maybe_copyable<false> {
maybe_copyable(const maybe_copyable&) = delete;
};
5. Conditionally explicit constructors
explicit specifiere.g
void sink(unique_ptr<X>);
void bath(pair<unique_ptr<X>, int>);
X x;
sink(&x); // fail
bath({&x, 1}); // uh-oh~ pass!
6. Using unique_ptr for exception safety
don't do this, the legacy no-no in c++98:
void no_no(new int(2), new int(3));
use unique_ptr to the rescue:
unique_ptr<int> arg1(new int(3));
unique_ptr<int> arg2(new int(4));
void yeah_yeah(arg1.release(), arg2.release());
std::unique_ptr::release(Ref: https://cplusplusmusings.wordpress.com/2015/03/09/simplifying-code-and-achieving-exception-safety-using-unique_ptr/ )
7. allocator_construct
std::allocator(ref: http://vsdmars.blogspot.com/2017/10/c-allocator-template-code.html )
By providing our own allocator, we could:
- log object creation/destruction
- register/deregister objects in global registry
- insert/ignore/change/rearrange constructor arguments.
- decide what's the default value for the objects created by the allocator.
8. tag dispatch
read:
We can use
if constexpr
instead of tag dispatch in C++17, which reduce writing specialized tag dispatch/overload functions.
~fin~
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.