From 853bbd1f438c5b95470db88797bde19bb382799a Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Fri, 29 Aug 2025 12:58:45 -0700 Subject: [PATCH] [common] enforce correct CRTP usage for mix-in classes (#11880) The mix-in helper classes like `Clearable`, `Equatable`, and `Unequatable` are intended for CRTP style inheritance, where `T` is the derived class itself. A mistaken inheritance, such as `class Foo : public Clearable`, can compile successfully but lead to subtle bugs. This change enforces the correct CRTP usage at compile time. By making the constructors of these helper classes `private` and declaring the derived template class `T` as a `friend`, any incorrect inheritance will now result in a build failure. This approach correctly detects such a mistake, even if `Foo` and `Bar` happen to be `friend`s of each other. Additionally, `Equatable` is updated to provide both `operator==` and `operator!=`, removing its dependency on `Unequatable`. This change allows us to apply the `private` constructor enforcement to `Equatable` as well. --- src/core/common/clearable.hpp | 5 +++++ src/core/common/equatable.hpp | 22 +++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/core/common/clearable.hpp b/src/core/common/clearable.hpp index 679f35c60..69ee467be 100644 --- a/src/core/common/clearable.hpp +++ b/src/core/common/clearable.hpp @@ -66,8 +66,13 @@ template void ClearAllBytes(ObjectType &aObject) */ template class Clearable { + friend Type; + public: void Clear(void) { ClearAllBytes(*static_cast(this)); } + +private: + Clearable(void) = default; }; } // namespace ot diff --git a/src/core/common/equatable.hpp b/src/core/common/equatable.hpp index f5cdc5f75..9f25f4333 100644 --- a/src/core/common/equatable.hpp +++ b/src/core/common/equatable.hpp @@ -50,6 +50,8 @@ namespace ot { */ template class Unequatable { + friend Type; + public: /** * Overloads operator `!=` to evaluate whether or not two instances of `Type` are equal. @@ -62,6 +64,9 @@ public: * @retval FALSE If the two `Type` instances are equal. */ bool operator!=(const Type &aOther) const { return !(*static_cast(this) == aOther); } + +private: + Unequatable(void) = default; }; /** @@ -72,8 +77,10 @@ public: * Users of this class should follow CRTP-style inheritance, i.e., the `Type` class itself should publicly inherit * from `Equatable`. */ -template class Equatable : public Unequatable +template class Equatable { + friend Type; + public: /** * Overloads operator `==` to evaluate whether or not two instances of `Type` are equal. @@ -87,6 +94,19 @@ public: { return memcmp(static_cast(this), &aOther, sizeof(Type)) == 0; } + + /** + * Overloads operator `!=` to evaluate whether or not two instances of `Type` are equal. + * + * @param[in] aOther The other `Type` instance to compare with. + * + * @retval TRUE If the two `Type` instances are not equal. + * @retval FALSE If the two `Type` instances are equal. + */ + bool operator!=(const Type &aOther) const { return !(*static_cast(this) == aOther); } + +private: + Equatable(void) = default; }; } // namespace ot