Last active
May 5, 2023 12:55
-
-
Save nthery/16334b622144d06bb489739bc91287e5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Of builders and temporary materialisation | |
void hotSpot() noexcept; | |
// ORIGINAL CODE | |
// RAII wrapper to start and stop a timer. | |
struct Timer { | |
// Start timer | |
explicit Timer(bool isFoo = true, bool isBar = false, bool isBaz = false); | |
// Stop and print timer | |
~Timer(); | |
Timer(const Timer&); | |
Timer& operator=(const Timer&); | |
Timer(Timer&&) noexcept; | |
Timer& operator=(Timer&&) noexcept; | |
}; | |
void client() { | |
// Measure time spent in hotSpot(). | |
// All these flags are confusing :-( | |
Timer timer(true, true); | |
hotSpot(); | |
} | |
// SUGGESTED (BUGGY) ALTERNATIVE | |
struct Timer2 { | |
// Start timer | |
explicit Timer2(); | |
// Stop and print timer | |
~Timer2(); | |
Timer2(const Timer2&); | |
Timer2& operator=(const Timer2&); | |
Timer2(Timer2&&) noexcept; | |
Timer2& operator=(Timer2&&) noexcept; | |
Timer2& setFoo(bool value); // { foo_ = value; return *this; } | |
Timer2& setBar(bool); | |
Timer2& setBaz(bool); | |
}; | |
void client2_ko() { | |
// Constructor is called twice => Timer starts twice :-( | |
auto timer = Timer2().setFoo(true).setBaz(false); | |
hotSpot(); | |
} | |
void client2_ok() { | |
// Constructor is called once ... but flags are not set :-( | |
auto timer = Timer2(); | |
hotSpot(); | |
} | |
// EXPLANATION | |
Timer newTimer(); | |
void returnByValue() { | |
auto timer = newTimer(); | |
hotSpot(); | |
// newtimer() returns by value | |
// (newTimer() expression is prvalue) | |
// C++ < 17: | |
// 1. Function call creates temporary object eagerly. | |
// 2. Compiler is allowed to optimize out call to copy/move constructor in | |
// copy-initialization (RVO) => single call to ctor *possible*. | |
// 3. But copy/move constructor must be accessible. | |
// C++ >= 17: | |
// 1. Function call does not create temporary object. | |
// 2. Copy-initialization (auto timer =) triggers materialisation of | |
// temporary object (prvalue to xvalue conversion) => single call to ctor *mandatory*. | |
// 3. Copy/move constructor does not need to be accessible. | |
} | |
void client2_ko_again() { | |
// - Call to constructor (Timer()) behaves as call to newTimer(). | |
// - Calling member access operator on prvalue is another trigger | |
// for temporary materialisation. | |
// - Once temporary object exists, copy constructor must be called. | |
// - Copy constructor rather than move constructor called because | |
// setFoo() return lvalue reference (could overload setFoo() with | |
// ref qualifier to return rvalue reference when called on rvalue reference) | |
auto timer = Timer2().setFoo(true); | |
hotSpot(); | |
} | |
// POTENTIAL (CORRECT BUT VERBOSE) ALTERNATIVE | |
struct TimerConfig { | |
// Set flags to default values | |
TimerConfig(); | |
TimerConfig& setFoo(bool); | |
TimerConfig& setBar(bool); | |
TimerConfig& setBaz(bool); | |
}; | |
struct Timer3 { | |
// Start timer | |
explicit Timer3(const TimerConfig&); | |
// Stop and print timer | |
~Timer3(); | |
Timer3(const Timer3&); | |
Timer3& operator=(const Timer3&); | |
Timer3(Timer3&&) noexcept; | |
Timer3& operator=(Timer3&&) noexcept; | |
}; | |
void client3() { | |
auto timer = Timer3(TimerConfig().setFoo(true).setBaz(false)); | |
hotSpot(); | |
} | |
// KEY POINTS | |
// - Strive to make function calls readable at call sites. | |
// - Function chaining can help with this... | |
// - ... but can lead to temporary object creation. | |
// - In C++17 temporary objects are created lazily. | |
// - Separation of concerns / Single Responsibilty Principle is | |
// good for your codebase. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment