lmi-commits
[Top][All Lists]
Advanced

[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;
 }



reply via email to

[Prev in Thread] Current Thread [Next in Thread]