c++c++11type-conversiondestructorpolicy-based-design

Policy conversion operator vs private destructor in policy-based class


In Modern C++ Design: Generic Programming and Design Patterns Applied Andrei Alexandrescu advocates for making the policies' destructor protected:

Because the destructor is protected, only derived classes can destroy policy objects, so it's impossible for outsiders to apply delete to a pointer to a policy class. The destructor, however, is not virtual, so there is no size or speed overhead

But later, he writes the following paragraph on policy compatibility:

As you can see, you have two-sided flexibility in implementing conversions between policies. You can implement a conversion constructor on the left-hand side, or you can implement a conversion operator on the right-hand side.

Let's say we have 2 policies:

class First{
public:
  First()             = default;
  First(const First&) = default;

protected:
  ~First() = default;
};

class Second{
public:

  explicit operator First() const {
    return //what???
  }

  Second()              = default;
  Second(const Second&) = default;
  Second(const First& )   {};

protected:
  ~Second() = default;
};

How does one create a conversion operator from policy Second to policy First without constructing a temporary object of type First?


Solution

  • The problem is that you cannot create objects with protected destructors, except from a derived class. A conversion operator creating such temporaries is therefore forbidden. One way to get around it is to make First and Second accepts each other through explicit constructors:

    #include <iostream>
    
    class First;
    class Second;
    
    class First
    {
    public:
        First()             = default;
        First(const First&) = default;
        explicit First(const Second&); 
        int value() const { return x; }    
    protected:
        ~First() = default;
    private:
        int x = 1;
    };
    
    class Second
    {
    public:
        Second()              = default;
        Second(const Second&) = default;
        explicit Second(const First&);
        int value() const { return x; }
    protected:
        ~Second() = default;
    private:
        int x = 2;
    };
    
    First::First(const Second& s): x(s.value()) {}
    Second::Second(const First& f): x(f.value()) {}
    

    You can then create a host class template Host<Policy> that has a templated conversion constructor that converts policies Policy and arbitrary U

    template<class Policy>
    class Host
    : 
        public Policy
    {
    public:    
        Host() = default;
    
        template<class U>
        Host(Host<U> const& other)
        :
            Policy(other)
        {}
    };
    
    int main()
    {
        Host<First> h1;
        Host<Second> h2;
        Host<Second> h3(h1);
        Host<First> h4(h2);
    
        std::cout << h1.value() << "\n";
        std::cout << h2.value() << "\n";
        std::cout << h3.value() << "\n";
        std::cout << h4.value() << "\n";
    }
    

    Live Example.

    Note that protected destructors and public inheritance are indeed recommended, but they are especially recommended to safely use "enriched" (i.e. stateful) policies. For stateless policies, one can also use protected inheritance and public destructors. For those policies, conversion operators can generate temporaries just fine.