This is part of my weekly C++ posts based on the daily C++ tips I make at my work. I strongly recommend this practice. If you dont have it in your company start it.
1. Capturing members by value
Imagine you want to return a lambda from an object that does some computations later but captures part of the state of that object:
class Boo {
public:
auto getValueMultiplier() {
return [=](int multiplier) {
return value * multiplier;
};
}
private:
int value = 5;
};
One can easily assume that value is captured by value but if we try to use it after the end of the lifetime of that object as we can see in the example the results are a little different.
What happens is that only the this pointer is captured by value and that data members are accessed via the captured this pointer - in fact it is this->value in the lambda. When the object is destroyed accessing value inside the lambda is like accessing random memory location.
To resolve this the Committee added capturing *this by value in C++17.
2. inline namespaces
Inline namespaces allow us to provide a form of versioning. It is basically a "default" nested namespace that is omitted when using the outer namespace.
namespace mylib {
inline namespace v1 {
void print() {
std::cout << "v1\n";
}
}
}
int main() {
mylib::print(); // note - v1 is omitted
}
Later we upgraded our library with new version of the function but we want the users to still be able to use the old functionality:
namespace mylib
{
inline namespace v2 { //we moved inline here
void print() {
std::cout << "v2\n";
}
}
namespace v1{
void print() {
std::cout << "v1\n";
}
}
}
int main(){
mylib::print(); // now we are using v2 without forcing the users of mylib to do anything
mylib::v1::print() //however they can still use the older version
}
This example on wandbox.
3. if/switch with initializer
4. Generic lambdas
Ah! The beauty of using the standard algorithms prior to C++14:
Luckily C++14 introduced generic lambdas where this becomes:
Note that the last one works with all classes that have size() method - not only unordered_map<wstring, vector<string>>.
The underlying implementation (as we all know lambdas are syntactic sugar forfunctors structs with operator() (we no longer call them functors - we use callables instead to avoid the collision with functors from the Category Theory where it means a completely different thing)
The underlying implementation should be something like this. This lambda :
is replaced by something like this:
In non-generic lambdas the operator() is not a template.
5. boost::flyweight
boost::flyweight is the Boost implementation of the famous Flyweight design pattern. The idea of this pattern is to save memory by avoiding duplication when you have a huge collection of elements which have a lot of identical immutable parts.
The usage is pretty straightforward. Having this struct, for example:
we convert it to:
and you also have a .get() method that returns a const T& to the underlying object
The thing that you should remember when switching to boost::flyweight is that it is designed to work with immutable data. You can not change the underlying objects but you can assign new value to the boost::flyweight object itself - it will rearrange its internals and will start pointing to the new immutable underlying object.
For deeper understanding of it you should read the documentation.
Bonus MSVC/Boost pro-tip that I've discovered yesterday - I'm big fan of semantic colorization but Boost uses additional file extensions - .ipp and .inl - that are not parsed as C++ by default. Apparently MSVC always had this capability to link arbitrary file extension to a language - Options/Text Editor/File Extension.
3. if/switch with initializer
Coming in C++17 the syntax of if/switch statements with initializer is as follows
if constexpr(optional) ( init-statement condition )Is syntactically equivalent to
statement-true
else
statement-false
{one benefit is that the variables declared in the init-statement just like in the for loop are not leaked in the ambient scope:
init_statement
if constexpr(optional) ( condition )
statement-true
else
statement-false
}
auto it = m.find(10);More examples can be found here (check this self-promoting out). Same goes for the switch statement:
if (it != m.end()) {
return it->size();
} // "it" is leaked into the ambient scope.
if (auto it = m.find(10); it != m.end()) {
return it->size();
} // "it" is destructed and than undefined
switch (Foo x = make_foo(); x.status())
{
case Foo::FINE: /* ... */
case Foo::GOOD: /* ... */
case Foo::NEAT: /* ... */
default: /* ... */
}
4. Generic lambdas
Ah! The beauty of using the standard algorithms prior to C++14:
for_each( begin(v), end(v), [](const decltype(*begin(v))& x) { cout << x; } );
sort( begin(w), end(w), [](const shared_ptr<some_type>& a,
const shared_ptr<some_type>& b) { return *a<*b; } );
auto size = [](const unordered_map<wstring, vector<string>>& m) { return m.size(); };
Luckily C++14 introduced generic lambdas where this becomes:
for_each( begin(v), end(v), [](const auto& x) { cout << x; } );
sort( begin(w), end(w), [](const auto& a, const auto& b) { return *a<*b; } );
auto size = [](const auto& m) { return m.size(); };
Note that the last one works with all classes that have size() method - not only unordered_map<wstring, vector<string>>.
The underlying implementation (as we all know lambdas are syntactic sugar for
The underlying implementation should be something like this. This lambda :
int multiplier = 2, sum = 0;
[multiplier, &sum](auto& item){ sum += item.Width() * multiplier; }
class _CompilerGeneratedNotReadable_
{
public:
_CompilerGeneratedNotReadable_(int& sum, int multiplyer) : sum_{s}, multiplier_{m} {}
template<class T>
void operator() (T& item) const
{
sum_ += item.Width() * multiplier_;
}
private:
int& sum_;
int multiplier_;
}
In non-generic lambdas the operator() is not a template.
5. boost::flyweight
boost::flyweight is the Boost implementation of the famous Flyweight design pattern. The idea of this pattern is to save memory by avoiding duplication when you have a huge collection of elements which have a lot of identical immutable parts.
The usage is pretty straightforward. Having this struct, for example:
struct contact_info {
std::string country;
std::string town;
...
};
we convert it to:
#include <boost/flyweight.hpp>
struct contact_info {
boost::flyweight<std::string> country;
boost::flyweight<std::string> town;
...
};
The flyweight object is implicitly convertible to const T&, for example:
std::string location;
Location += info.country + " " + info.town;
The thing that you should remember when switching to boost::flyweight is that it is designed to work with immutable data. You can not change the underlying objects but you can assign new value to the boost::flyweight object itself - it will rearrange its internals and will start pointing to the new immutable underlying object.
For deeper understanding of it you should read the documentation.
Bonus MSVC/Boost pro-tip that I've discovered yesterday - I'm big fan of semantic colorization but Boost uses additional file extensions - .ipp and .inl - that are not parsed as C++ by default. Apparently MSVC always had this capability to link arbitrary file extension to a language - Options/Text Editor/File Extension.
No comments:
Post a Comment