[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master 48007e3c 09/13: Support an abstract-xor-final
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master 48007e3c 09/13: Support an abstract-xor-final hierarchy |
Date: |
Wed, 27 Jul 2022 15:16:34 -0400 (EDT) |
branch: master
commit 48007e3c1edcc1bc5a776e8e61f2b92df6ebea56
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>
Support an abstract-xor-final hierarchy
---
crtp_base.hpp | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
crtp_base_test.cpp | 58 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 136 insertions(+)
diff --git a/crtp_base.hpp b/crtp_base.hpp
index 53890cef..1ffa967b 100644
--- a/crtp_base.hpp
+++ b/crtp_base.hpp
@@ -119,6 +119,84 @@ class uncopyable
}
};
+/// Root class for a polymorphic hierarchy
+///
+/// A polymorphic hierarchy's root class generally has a virtual dtor.
+/// Declaring a dtor inhibits implicit declaration of certain special
+/// member functions (move functions are non-declared; copy functions
+/// are declared in C++20, but that behavior is deprecated), so the
+/// root class must declare them explicitly if they are wanted (as is
+/// most often the case). Deriving from class polymorphic_base and
+/// relying on the Rule of Zero is preferable to copying and pasting
+/// these six declarations into every class.
+///
+/// CRTP rationale: same as class uncopyable.
+
+template<typename T>
+class polymorphic_base
+{
+ protected:
+ polymorphic_base() = default;
+ virtual ~polymorphic_base() = default;
+ polymorphic_base(polymorphic_base const&) = default;
+ polymorphic_base(polymorphic_base&&) = default;
+ polymorphic_base& operator=(polymorphic_base const&) = default;
+ polymorphic_base& operator=(polymorphic_base&&) = default;
+};
+
+// This gcc warning appears to be invalid: class abstract_base has
+// (public) implicitly-declared special member functions.
+#if defined __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wctor-dtor-privacy"
+#endif // defined __GNUC__
+
+/// Root class for an abstract-xor-final hierarchy
+///
+/// "Make every class in a polymorphic hierarchy abstract or final" is
+/// a reasonable guideline, which this class is intended to support.
+/// To be abstract, it must have at least one pure virtual function.
+/// It is often recommended to make the dtor pure, especially if no
+/// other function is an obvious candidate. Instead, this class uses
+/// concrete_if_not_pure() for that purpose. Rationale:
+/// - First and foremost, if the dtor is the only pure function,
+/// then derived classes are concrete by default. Making non-leaf
+/// derived classes abstract by repeatedly declaring each one's
+/// dtor pure requires extra work, which is too easily overlooked;
+/// and neither gcc-11 nor clang-13 offers a warning option that
+/// identifies overlooked cases. But using concrete_if_not_pure()
+/// instead of a pure dtor makes derived classes both polymorphic
+/// and abstract by default.
+/// - Second, because a pure-specifier cannot be combined with a
+/// default definition on the same line, a pure dtor requires an
+/// out-of-line definition--omission of which is an error that
+/// compilers diagnose, so this poses no danger, but it does
+/// increase verbosity in each non-leaf class.
+/// This approach does require defining concrete_if_not_pure() in each
+/// leaf class, but compilers enforce constructibility for classes
+/// that are actually instantiated, so there's no risk if this is
+/// overlooked. Adding that one line is a reasonable tradeoff for
+/// eliding several lines of boilerplate thanks to the Rule of Zero.
+///
+/// Unfortunately, the "final" keyword must be typed manually in order
+/// to complete the "abstract or final" paradigm. Code such as
+/// static_assert(std::is_final_v<T>);
+/// could be pasted into the concrete_if_not_pure() function body to
+/// ensure a compiler warning if desired.
+///
+/// CRTP rationale: same as class uncopyable.
+
+template<typename T>
+class abstract_base : private polymorphic_base<T>
+{
+ private:
+ virtual void concrete_if_not_pure() = 0;
+};
+
+#if defined __GNUC__
+# pragma GCC diagnostic pop
+#endif // defined __GNUC__
+
} // namespace lmi
#endif // crtp_base_hpp
diff --git a/crtp_base_test.cpp b/crtp_base_test.cpp
index 85922081..4efd72be 100644
--- a/crtp_base_test.cpp
+++ b/crtp_base_test.cpp
@@ -44,8 +44,66 @@ void test_uncopyable()
static_assert(!std::is_move_assignable_v <X>);
}
+// Rule of Zero: all special members are defaulted.
+
+class B0 : private lmi::abstract_base<B0> {};
+class B1 : private lmi::abstract_base<B1> {};
+class C : private B0, private B1 {};
+
+#if defined __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wctor-dtor-privacy"
+#endif // defined __GNUC__
+class D final : private C
+{
+ private:
+ void concrete_if_not_pure() override {}
+};
+#if defined __GNUC__
+# pragma GCC diagnostic pop
+#endif // defined __GNUC__
+
+/// Test abstract-xor-final hierarchy.
+
+void test_abstract_or_final()
+{
+ static_assert( std::is_abstract_v <B0>);
+ static_assert( std::is_abstract_v <B1>);
+ static_assert( std::is_abstract_v <C>); // abstract by inheritance
+ static_assert(!std::is_abstract_v <D>);
+
+ static_assert(!std::is_final_v <B0>);
+ static_assert(!std::is_final_v <B1>);
+ static_assert(!std::is_final_v <C>);
+ static_assert( std::is_final_v <D>);
+
+ static_assert(!std::is_default_constructible_v <B0>);
+ static_assert(!std::is_default_constructible_v <B1>);
+
+ static_assert(!std::is_default_constructible_v <C>);
+ static_assert( std::is_destructible_v <C>);
+ static_assert(!std::is_copy_constructible_v <C>);
+ static_assert(!std::is_move_constructible_v <C>);
+ static_assert( std::is_copy_assignable_v <C>);
+ static_assert( std::is_move_assignable_v <C>);
+
+ static_assert( std::is_default_constructible_v <D>);
+ static_assert( std::is_destructible_v <D>);
+ static_assert( std::is_copy_constructible_v <D>);
+ static_assert( std::is_move_constructible_v <D>);
+ static_assert( std::is_copy_assignable_v <D>);
+ static_assert( std::is_move_assignable_v <D>);
+
+ D da; // D()
+ D db(da); // D(D const&)
+ db = da; // D& operator=(D const&)
+ D dd {std::move(da)}; // D(D&&)
+ db = std::move(da); // D& operator=(D&&)
+}
+
int test_main(int, char*[])
{
test_uncopyable();
+ test_abstract_or_final();
return 0;
}
- [lmi-commits] [lmi] master updated (6126d1e0 -> a07a61b8), Greg Chicares, 2022/07/27
- [lmi-commits] [lmi] master 7649cf4d 05/13: Modernize class uncopyable and its documentation, Greg Chicares, 2022/07/27
- [lmi-commits] [lmi] master 41339667 06/13: Rename uncopyable_lmi.hpp -> crtp_base.hpp, Greg Chicares, 2022/07/27
- [lmi-commits] [lmi] master 48007e3c 09/13: Support an abstract-xor-final hierarchy,
Greg Chicares <=
- [lmi-commits] [lmi] master a07a61b8 13/13: Declutter, Greg Chicares, 2022/07/27
- [lmi-commits] [lmi] master 22a759fa 12/13: Devirtualize, Greg Chicares, 2022/07/27
- [lmi-commits] [lmi] master f12819ba 02/13: Make md5sum_for_file ctor noexcept, Greg Chicares, 2022/07/27
- [lmi-commits] [lmi] master 6e590f5c 01/13: Work around a spurious gcc 12 -Wnull-dereference, Greg Chicares, 2022/07/27
- [lmi-commits] [lmi] master 0c2fceb0 04/13: Resurrect 'uncopyable_lmi.hpp', Greg Chicares, 2022/07/27
- [lmi-commits] [lmi] master cde94ff9 10/13: Use an abstract-xor-final hierarchy, Greg Chicares, 2022/07/27
- [lmi-commits] [lmi] master efa79f5c 07/13: Establish unit tests for CRTP convenience classes, Greg Chicares, 2022/07/27
- [lmi-commits] [lmi] master 4ebf92ed 03/13: Suppress warnings for deprecated unary_function in cgicc, Greg Chicares, 2022/07/27
- [lmi-commits] [lmi] master 21685250 11/13: Use another abstract-xor-final hierarchy, Greg Chicares, 2022/07/27
- [lmi-commits] [lmi] master 2143f359 08/13: Add unit tests for CRTP convenience classes, Greg Chicares, 2022/07/27