Proposal to add Deletion Traits to the Standard Library

Doc. no.: J16/03-0091 = WG21/N1508
Date:     13 September 2003
Project:  Programming Language C++
Reply To: Bronek Kozicki <brok@rubikon.pl>

Contents

1. Motivation
2. Design, requirements and proposal text
3. Proposal
  3.1. is_safely_destructible
  3.2. has_virtual_destructor
4. Discussion
  4.1. Alternatives
  4.2. Rationale
  4.3. Other uses of proposed traits
5. References
6. Acknowledgements

1. Motivation

This proposal should be considered as addition to "Type Traits Proposal", available in document N1424 [1] wrote by John Maddock. Main point of presented proposal is to enable detection in compile time if particular design requirement has been met in class design. This requirement is ability to safely destroy object or in other words obey Liskov Substitution Principle in regard to object destruction . This is particularly important in generic programming, where specific properties (or applicability for specific purpose) of class being used may remain unknown at the point of object creation, pointer cast or deletion. Currently there are no means in language to protect against class of problems presented bellow (assuming weak coupling between parts of presented code):

template <typename BASE, typename DERIVED>
BASE* Create() {
  return new DERIVED();
}
class Base{};
class Derived : public Base{};
Base* BaseFactory()
{
  return Create<Base, Derived>();
}
void BaseOwner()
{
  Base* b = BaseFactory();
  // ...
  delete b;
  // undefined behaviour per clause 5.3.5/3 of C++ Standard
}

Following proposal is an attempt to deliver tools which can be used by generic programs to ensure well defined behaviour when objects of unknown type are being dynamically created, passed between loosely-coupled parts of software and then deleted.

2. Design, requirements and proposal text

Proposed traits share design constraints presented by John Madock in type traits proposal [1], as well as parts of proposal text. Proposed traits fall in unary type traits, type properties category of type traits proposal. Proposed traits are using integral_constant template class, which is part of type traits proposal.

3. Proposal

Two different traits are being proposed, where each could be used to impose different set of restritions on class design - namely is_safely_destructible (author of this proposal believes that better name could be invented) and has_virtual_destructor. It's important to note that these traits cannot be implemented without compiler support; currently there are no means in C++ language necessary to implement them. For this reason there are no existing implementations, nor usage experience. At the end of this paper sample usage of proposed traits can be found. Another important point is that these traits will remove some freedom from the programmer on class design, as properties being inspected by traits are currently unavailable to class user; however author of this proposal believes that discussed properties belong to class interface (not implementation) and thus giving this access will not break separate interface from implementation rule. For the same reason, these traits can be used to circumvent "as if" rule. Proposed traits do not introduce runtime overhead; these traits are providing static type properties, thus their values are static const, defined at compile time. This proposal does not change existing language rules, nor syntax or meaning of existing keywords.

3.1. is_safely_destructible

template <class T> struct is_safely_destructible{
  static const bool value = implementation_defined;
  typedef bool value_type;
  typedef integral_constant<value_type, value> type;
  operator type()const;
}

value is defined to be false for any class which inherits publicly available, non-virtual destructor; it's true otherwise. Class is said to inherit publicly available non-virtual destructor, if any of its public base classes has publicly accessible destructor which is not virtual. Note: this trait does not imply that class is polymorphic; per presented above rule, value of this trait is also true for class which has public non-virtual destructor, but does not inherit publicly from any other class having such destructor. In other words: this trait inspects not class alone, but root of its inheritance hierarchy. It's believe of author of this proposal, that publicly inherited classes should be considered part of class interface, thus this proposal will not break separate interface from implementation rule. This trait does not undisclose property of class if it is on top of inheritance hierarchy or inherits from other class/es, as long as it does not inherit destructors which can be used to delete object causing undefined behaviour per clause 5.3.5/3. This trait is mostly useful at point of object construction, ie. where dynamic type of object being created is known at compile time, to ensure well-defined behaviour during object deletion, where its known that object ownership will be passed to pointer to any (unknown at point of object construction) of its base classes.

Examples:

  class Base1{protected: ~Base1();};
    is_safely_destructible<Base1>::value == true;
    // class Base1 does not inherit from any other class
  class Derived1 : public Base1{};
    is_safely_destructible<Derived1>::value == true;
    // Derived1 inherits from Base1, which does not have public destructor
  class Derived2 : public Base1{public: virtual ~Derived2();};
    is_safely_destructible<Derived2>::value == true;
    // As Derived1
  class Base2{public: ~Base2();};
   is_safely_destructible<Base2>::value == true;
    // As Base1
  class Derived3 : protected Base2 {public: virtual ~Derived3();};
    is_safely_destructible<Derived3>::value == true;
    // Base2 is not public base class of Derived3
  class Derived4 : public Derived3, public Base1 {};
    is_safely_destructible<Derived4>::value == true;
    // base class Derived3 has virtual destructor
    // and base class Base1 does not have public destructor
  class Derived5 : public Base2{public: virtual ~Derived5();};
    is_safely_destructible<Derived5>::value == false;
    // inherits Base2::~Base2 , which is public and not virtual
  class Base3 {protected: virtual ~Base3();};
    is_safely_destructible<Base3>::value == true;
    // Base3::~Base3 is not public
  class Derived6 : private Base2{};
    is_safely_destructible<Derived6>::value == true;
    // Derived6 does not inherit publicly from Base2
  class Derived7 : public boost::noncopyable{};
    is_safely_destructible<Derived7>::value == true;
    // boost::noncopyable does not have public destructor
  class Derived8 : public Base2{protected: ~Derived8();};
    is_safely_destructible<Derived8>::value == false ;
    // inherits Base2::~Base2 , which is public and not virtual

Class boost::noncopyable is part of boost library [2].

This proposal does not protect against situations where object is being deleted by friend class/function, or using delete this; idiom. Such code is considered part of class implementation, thus no solution is necessary. In cases where programmer wants to ensure additional protection (not incuring runtime cost), has_virtual_destructor trait (presented below) is applicable.

3.2. has_virtual_destructor

template <class T> struct has_virtual_destructor{
  static const bool value = implementation_defined;
  typedef bool value_type;
  typedef integral_constant<value_type, value> type;
  operator type()const;
}

value is defined to be true if class has virtual destructor (no matter its access level); false otherwise. Author of this proposal believes that polymorphism of class in regard to its destructor is part of class interface (not implementation detail), and thus this trait does not break separate interface from implementation rule. This trait is mostly useful at two points of the program:

Examples (class definitions as above):

  has_virtual_destructor<Base1>::value == false;
  has_virtual_destructor<Derived4>::value == true;
  has_virtual_destructor<Base2>::value == false;
  has_virtual_destructor<Base3>::value == true;
  has_virtual_destructor<Derived7>::value == false;

4. Discussion

Meaning of proposed trait is_safely_destructible is that dynamically created object of given class can be safely (ie. not invoking undefined behaviour, per clause 5.3.5/3, first sentence) destroyed using pointer to any of its base classes. This allows class user to impose restrictions on class design, but offers most complete solution to given problem. These restrictions are being imposed not only on class alone, but also on all classes it publicly derives from. To release these restrictions, trait has_virtual_destructor can be used in most situations, namely at the point of pointer cast to pointer to base class or at the point of object deletion.

4.1. Alternatives

Other possible solutions (presented here for discussion, but not proposed in this document) are:

This is not a good solution, as it would make illegal code that is perfectly legal currently (when dynamic type of pointee is the same as the static type of pointer, ie. polymorphic class is not abstract). This is also not a full solution, as type of pointed class might not be polymorphic, and under such condition possible undefined behaviour would remain undetected. It is possible (but not proposed in this paper) to add more rules to the language, like:

but such rules would make invalid many programs, which currently are perfectly valid and do not expose undefined behaviour. In opinion of author, this is definitely not a way to go.

4.2. Rationale

Proposed solution is based on assumption that at the point of dynamic object creation, there usually is strategy of its destruction. Possible strategies are:

  1. own (do not pass ownership) the object for its whole lifetime, and destroy it using pointer to static type identical to dynamic type of pointee (ie. object being created). In such case there is no need to impose any additional (besides those already imposed by C++ language, ie. accessibility of destructor at the point of object deletion) restriction on said class. Proposed traits have no value if case this strategy has been chosen, because problem these traits are designed to solve, does not appear. This strategy has been chosen by authors of smart pointer boost::shared_ptr [3] . Author of this proposal believes that similar strategy could be adopted by std::auto_ptr, and proposed traits could be used to make implementation efficient when possible (intruducing very small runtime cost for objects which are "safe to delete through pointer to every base class"). This addition is left as an idea for another proposal.

  2. pass ownership of the object using pointers to some predefined set of classes (including predefined set of its base classes), or destroy it using pointer to any of such predefined classes. Trait has_virtual_destructor can be used to allow verification at compile time that these predefined base classes have virtual destructors. This trait can be used at the point of object construction (verifying predefined set of base classes), destruction (verifying static type of object pointer), or cast statement (at the point where ownership of object is being passed to pointer to its base class) . Example trait usage:

    template <typename BASE, typename DERIVED>
    BASE* create() {
      static_assert(has_virtual_destructor<BASE>::value, "bad cast");
      return new DERIVED();
    }
    class Base{}; class Derived : public Base{};
    int main() {
    Base* b = create<Base, Derived>();  // static assertion
    //...
    delete b;
    }
    

    static_assert is part of different proposal[4].

    Please note that base class used to cast pointer object being created is know at the point of object construction. Thus has_virtual_destructor can be used here, to ensure well-defined behaviour at the point of its destruction. Problem with this trait is that it requires predefined set of base classes at point of object construction, thus it introduces strong coupling between class interface and point of its construction. Trait is_safely_destructible can be used to avoid it.

  3. pass ownership of the objects to loosely-coupled part of program, which may further cast it to unknown (at the point where objects is being created or passed) class. In this case creator of object may use is_safely_destructible trait in regard to dynamic class type (at point of its construction) to ensure that no matter how object is being cast, its destruction will not expose undefined behaviour.

    class BaseA{public: virtual ~BaseA(){}}; class BaseB{};
    class Derived : public BaseA, public BaseB{};
    template <typename CLASS>
    std::auto_ptr<CLASS> create() {
      static_assert(is_safely_destructible<CLASS>::value, "unsafe class");
      return new CLASS();
    }
    int main() {
    std::auto_ptr<BaseB> = create();  // static assertion
    //...
    }
    

    Please note, that at the point of object construction only dynamic type of object is known; function create is then passing ownership of the object to its client, presuming that client will cast this object to some (unknown at the point of object construction) type. Please note that this sample code is very simplified; in real-world code there would be very weak coupling between creator and its clients, thus definition of dynamic type could be unavailable from client code, and at the same time creator would not know interfaces being implemented by class just created. Use of is_safely_destructible allows creator to ensure well defined behaviour during object destruction, no matter how its going to be used by its new owner. Following code is example of loosely-coupled class factory, which may benefit from trait is_safely_destructible.

    class factory : boost::noncopyable {
    public:
      template <class Product>
      static bool register_class (std::string const& identifier)
      {
        instance().creators[identifier] = &create<Product>;
        return true;
      }
      template <class ProductBase>
      static bool create(std::string const& identifier, std::auto_ptr<ProductBase>& dest)
      {
        create_map::const_iterator i = instance().creators.find(identifier);
        if (i == instance().creators.end())
          return false;
        std::auto_ptr<base> product(i->second());
        if (ProductBase* b = dynamic_cast<ProductBase*>(product.get()))
        {
          product.release();
          dest.reset(b);
          return true;
        }
        return false;
      }
    private:
      factory() {}
    ~factory() {}
      static factory& instance()
      {
        static factory instance;
        return instance;
      }
      class base {public: virtual ~base() {}};
      typedef std::auto_ptr<base> (*create_function)();
      typedef std::map<std::string, create_function> create_map;
      template <class Product>
      static std::auto_ptr<base> create()
      {
        struct derived : public Product, public base {};
        static_assert(has_virtual_destructor<Product>::value), "Given class does not have virtual destructor")
    // This assert is satisfied even if Product inherits public, non-virtual destructor
        static_assert(is_safely_destructible<derived>::value, "Unable to safely inherit from given class");
    // This assert fully verifies that Product class can be safely derived from
        return std::auto_ptr<base>(new derived);
      }
      create_map creators;
    };
    class Shape {public: virtual ~Shape() {} /* ... */};
    class Circle : public Shape {/* ... */};
    factory::register_class<Circle>("circle");
    // This code compiles fine
    class Horse {public: virtual ~Horse() {} /* ... */};
    class Donkey {public: ~Donkey() {} /* ... */}
    class Mule : public Horse , public Donkey {/* ... */};
    factory::register_class<Mule>("mule")
    // Forces instantiation of factory::create<Mule> ,
    // which defines struct base : public Mule, public base
    // and triggers static assertion on is_safely_destructible<derived>
    

    Class factory presented above was orginally wrote by Richard Smith [5], and modified by author of this proposal as an example of traits usage.

4.3. Other uses of proposed traits

Trait is_safely_destructiblecan used to verify that class can be safely used as a base class:

template <typename T> class is_safe_as_base{
   struct A : public T {};
public:
   typedef bool value_type;
   static const value_type value = is_safely_destructible<A>::value;
   typedef integral_constant<value_type,value> type;
   operator type()const;
};

5. References

[1] http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1424.htm
[2] http://www.boost.org/libs/utility/utility.htm#Class_noncopyable
[3] http://www.boost.org/libs/smart_ptr/shared_ptr.htm
[4] http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1381.htm
[5]  http://groups.google.pl/groups?as_umsgid=Pine.LNX.4.55.0309040946040.18458@sphinx.mythic-beasts.com

6. Acknowledgements

Many thanks to David Abrahams, John Maddock, Witold Kuzminski and my wife Malgosia