Skip to content

Instantly share code, notes, and snippets.

@nthery
Last active May 5, 2023 12:55
Show Gist options
  • Save nthery/16334b622144d06bb489739bc91287e5 to your computer and use it in GitHub Desktop.
Save nthery/16334b622144d06bb489739bc91287e5 to your computer and use it in GitHub Desktop.
// 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