Better_Software_Header_MobileBetter_Software_Header_Web

Find what you need - explore our website and developer resources

Pimpl for Small Classes

class WiperSettings {
public:
  enum class Status : int8_t { Off, Slow, Medium, Fast };

  EXPORT WiperSettings();
  EXPORT ~WiperSettings();
  EXPORT WiperSettings::WiperSettings(const WiperSettings &);
  EXPORT WiperSettings &operator=(const WiperSettings &);

  inline WiperSettings(WiperSettings &&) noexcept;
  inline WiperSettings &operator=(WiperSettings &&) noexcept;
  inline void swap(WiperSettings &) noexcept);

  EXPORT Status getStatus() const;
  EXPORT void setStatus(Status);

private:
   Status s;  // current inline state, small
   class WiperSettingsPrivate *d;  // dpointer for future expansion
};
SMFRight now (no dpointer used)In the future (use dpointer)Remarks
Default ctorInitialize the status; initialize the d-pointer to nullptr. Although the d-pointer is unused at this moment, we’re going to default other SMFs and we don’t want UB to be triggered by reading uninitialized data.Initialize the status and allocate the pimpl.In the future, it will need to change behavior and have access to the complete private class; so, it must be exported and out of line.
DtorDefault implementation (destroy the members).Destroy the members and deallocate the pimpl.Same as above.
Copy ctorDefault implementation (member-wise copies).Allocate a new pimpl by copying from the existing one.Same as above.
Copy assignmentDefault implementation (member-wise assignments).Copy and swap, or a more ad-hoc strategy.While copy and swap is universal and could potentially be inlined, it’s not necessarily the most efficient implementation. If the class is extended, one may need to switch away from copy and swap to a much more efficient strategy. The allocation cost in the copy is going to dwarf the cost of the out of line call anyway.
Move ctorMove status and d from the source and reset other.d to nullptr, even if we’re not using the d-pointer right now (⚠️). See below.←←← Has to be the same — it’s inline!The move constructor can and should be fully inline. Making it out of line introduces overhead for no good reason.
Move assignmentMove and swap.←←← Has to be the same — it’s inline!Fully inline as well. Could be implemented in terms of pure swap, but in Qt we prefer the determinism of move and swap when we cannot be sure that a class only holds memory (see here).
SwapSwap status and swap d, even if we’re not using the d-pointer right now.←←← Has to be the same — it’s inline!Fully inline as well.
class WiperSettings {
public:
  ~~~
private:
   union {    // anonymous
      Status s;
      WiperSettingsPrivate *d;
   };
};
SMFRight now (no dpointer used)In the future (use dpointer)
Default ctorInitialize s.Initialize d by allocating the pimpl.
DtorDefault implementation (destroy the members).Deallocate d.
Copy ctorDefault implementation (member-wise copies).Allocate a new pimpl by copying from the existing one.
Copy assignmentDefault implementation (member-wise assignments).Copy and swap, or a more ad-hoc strategy.
Move ctor⚠️⚠️⚠️←←← Has to be the same, it’s inline!
Move assignmentMove and swap.←←← Has to be the same, it’s inline!
Swap⚠️⚠️⚠️←←← Has to be the same, it’s inline!
WiperSettings::WiperSettings(WiperSettings &&other) noexcept
    : d(std::exchange(other.d, nullptr)) {}
class WiperSettings {
public:
  ~~~
private:
   union U {
      Status s;
      WiperSettingsPrivate *d;
   } u;
};
WiperSettings::WiperSettings(WiperSettings &&other) noexcept
    : u(other.u)
{
    other.u.d = nullptr;
}

void WiperSettings::swap(WiperSettings &other) noexcept
{
    using std:swap; swap(u, other.u);
}

About KDAB


2 Comments

29 - Jun - 2023

Piegiorgio

29 - Jun - 2023

Giuseppe D'Angelo

GiuseppeD'Angelo

Giuseppe D’Angelo

Senior Software Engineer

Learn Modern C++

Learn more