Clarification about Sean Parent's talk “Inheritance is the base class of evil”

前端 未结 1 1051
别那么骄傲
别那么骄傲 2021-01-30 09:28

Sean Parent\'s talk, Inheritance is the base class of evil, says that polymorphism is not a property of the type, but rather a property of how it is used. As a thumb rule, don\'

1条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2021-01-30 10:11

    Type erasure 101:

    Step 1: make a regular (or semi-regular move-only) type that hides the detail.

    struct exposed_type;
    

    This class exposes the concepts you want to support. Copy, move, destroy, equals, total order, hash, and/or whatever custom concepts you need to support.

    struct exposed_type {
      exposed_type(exposed_type const&);
      exposed_type(exposed_type&&);
      friend bool operator<(exposed_type const&, exposed_type const&);
      friend std::size_t hash(exposed_type const&);
      // etc
    };
    

    Many of these concepts can be roughly mapped from a pure virtual interface method in your current inheritance based solution.

    Create non-virtual methods in your Regular type that expresses the concepts. Copy/assign for copy, etc.

    Step 2: Write a type erasure helper.

    struct internal_interface;
    

    Here you have pure virtual interfaces. clone() for copy,etc.

    struct internal_interface {
      virtual ~internal_interface() {}
      virtual internal_interface* clone() const = 0;
      virtual int cmp( internal_interface const& o ) const = 0;
      virtual std::size_t get_hash() const = 0;
      // etc
      virtual std::type_info const* my_type_info() const = 0;
    };
    

    Store a smart pointer1 to this in your Regular type above.

    struct exposed_type {
      std::unique_ptr upImpl;
    

    Forward the regular methods to the helper. For example:

    exposed_type::exposed_type( exposed_type const& o ):
      upImpl( o.upImpl?o.upImpl->clone():nullptr )
    {}
    exposed_type::exposed_type( exposed_type&& o )=default;
    

    Step 3: write a type erasure implementation. This is a template class that stores a T and inherits from the helper, and forwards the interface to the T. Use free functions (sort of like std::begin) that uses methods in the default implementation if no adl free function was found.

    // used if ADL does not find a hash:
    template
    std::size_t hash( T const& t ) {
      return std::hash{}(t);
    }
    template
    struct internal_impl:internal_interface {
      T t;
      virtual ~internal_impl() {}
      virtual internal_impl* clone() const {
        return new internal_impl{t};
      }
      virtual int cmp( internal_interface const& o ) const {
        if (auto* po = dynamic_cast(&o))
        {
          if (t < *po) return -1;
          if (*po < t) return 1;
          return 0;
        }
        if (my_type_info()->before(*o.my_type_info()) return -1;
        if (o.my_type_info()->before(*my_type_info()) return 1;
        ASSERT(FALSE);
        return 0;
      }
      virtual std::size_t get_hash() const {
        return hash(t);
      }
      // etc
      std::type_info const* my_type_info() const {
        return std::addressof( typeid(T) ); // note, static type, not dynamic
      }
    };
    

    Step 4: add a constructor to your regular type that takes a T and constructs a type erasure implementation from it, and stuffs that in its smart pointer to the helper.

    template>::value, int>* =nullptr
    >
    exposed_type( T&& t ):
      upImpl( new internal_impl>{std::forward(t)} )
    {}
    

    After all this work, you now have non-intrusive polymorphic system with a regular (or semi-regular) value type.

    Your factory functions return the regular type.

    Look into sample implementations of std::function to see this done fully.


    1 both unique and shared are good choices, depending on if you want to store immutable/copy on write data, or manually clone.

    0 讨论(0)
提交回复
热议问题