To Grin Again

To Grin Again

By Alan Griffiths

Overload, 14(72):, April 2006


In the latter years of the last millennium there were a number of articles published that explored the design space of “smart pointers”. That was in the days where there were loads of broken implementations available for download from the internet, but working ones – like those in the Loki and Boost libraries – were not to be found.

One of these pointers has come back to haunt me – partly because I wrote it, but also because it meets a set of recurring requirements. I’d not looked at it for years, but a colleague was implementing a “Cheshire Cat” class using Boost’s shared_ptr [1] (which is a dubious choice as the copy and assignment semantics are completely wrong for this purpose) so I pointed him to my old article on grin_ptr [2]. This article has also been referenced by a couple of recent Overload articles, so I thought it was time to revisit the subject.

The first thing I discovered is that I’ve never submitted the article for publication in Overload (instead a heavily annotated version written with Allan Newton appeared in Overload 38 [3]). The second thing I discovered is that there were a number of improvements I’d like to make to both the code and to the article. What follows is the result.

The “Classical” Cheshire Cat

The “Cheshire Cat” idiom is a great solution to a set of problems that have existed since the pre-history of C++. This idiom first emerged in the late ‘80s in a cross-platform GUI class library called CommonView and was described by John Carolan [4]. It has been reinvented a number of times since and is also known as “Compilation Firewall” and as “Pimpl”. The “Gang of Four” [5] classifies it as a special case of the “Bridge” pattern. These problems addressed by Cheshire Cat stem from three areas:

  1. Ensuring that a header file includes nothing that the client code needn’t be aware of. (In the case of CommonView, this includes the native windowing APIs.)

  2. Making it possible to distribute updated object code libraries without requiring the client code to be recompiled. If a class definition (or anything else in a header) changes, then any compilation unit that includes it must be recompiled.

  3. Protecting intellectual property by concealing implementation techniques used.

Which of these is most important to you will differ according to circumstance, but the “Cheshire Cat” idiom addresses them all.

When Glockenspiel (the suppliers of CommonView) started developing their cross-platform GUI library they were influenced by all three of the above reasons for hiding the implementation. Here is a simplified telephone list class implemented in the style of the CommonView library:

struct phone_list_implementation; 

class phone_list 
{ 
public: 
    phone_list(const char *name); 
    phone_list(const phone_list& rhs); 
    phone_list& operator=(const phone_list& rhs); 
    ~phone_list(); 

    const char* list_name(); 
    const char* find_number(const char *person); 
    void add(const char *name, 
    const char *number); 

private: 
    phone_list_implementation* grin; 
}; 

Note the need for a copy constructor and assignment operator. These are required because phone_list owns the phone_list_implementation to which it holds a pointer. (The default behaviour of just copying the pointer value is obviously inappropriate.)

In the implementation file we can imagine that the phone_list member functions are implemented like this:

const char* phonelist::find_number(const char* person) 
{ 
    return grin->number(person); 
} 

The Motivation for grin_ptr

Once you’ve written a few classes using this idiom it will occur to you that you are writing the same functions again and again to manage the implementation. Specifically the copy constructor, assignment operator and destructor of a Cheshire Cat class are generic - they always look the same, they only differ in the types of the interface and implementation classes.

This sounds like a job for a template. A template that looks after an implementation object allocated on the heap, and ensures it is copied or deleted when appropriate. It is tempting to reach for the standard library, but the closest thing we find there is auto_ptr<> . Even moving further afield to Boost we don't find a smart pointer with the desired semantics.

Let us suppose for a moment that such a smart pointer exists and is called arg::grin_ptr<> . This would allow the phone_list class to be rewritten as follows:

class phone_list 
{ 
public:     
    explicit phone_list(std::string const& name); 

    std::string name() const; 

    std::pair<bool, std::string> number(std::string const& person) const; 

    phone_list& add(std::string const& name, std::string const& number); 

private: 
    class implementation; 
    arg::grin_ptr<implementation> grin; 
}; 

Note that there is no longer a need to supply the copy constructor, assignment operator, or destructor as we are assuming that the necessary logic is supplied by the grin_ptr<> template. (I’ve also taken the opportunity to use a more contemporary style of C++, by nesting implementation, using const and std::string but the ideas are the same.)

In the implementation file we can imagine that the phone_list member functions are implemented the same way as in the original. For example:

std::pair<bool, std::string> 
phonelist::number(std::string const& person) const 
{ 
    return grin->number(person); 
} 

The idea is for grin_ptr<> to act “just like” a pointer in all relevant ways while taking the burden of ownership management. Naturally, “all relevant ways” doesn’t include such things as support for pointer arithmetic since this is inappropriate for pointers used in implementing “Cheshire Cat”.

By using a compatible substitute for a “real” pointer we avoid the need to write repetitive boilerplate code. Everything else is the same.

The resulting simplification looks good, but can such a smart pointer be implemented?

Implementing arg::grin_ptr<>

The principle problem to overcome in implementing grin_ptr<> is that the compiler generated copy constructor, assignment operator and destructor for phone_list will each instantiate the corresponding code for grin_ptr<> in a context where implementation is an incomplete type.

This means that the grin_ptr<> copy constructor cannot simply copy construct an instance of implementation . Likewise, the destructor of grin_ptr<> can’t be a simple delete p; as deleting a pointer to an incomplete type gives undefined behaviour whenever the type in question has a non-trivial destructor. The assignment operator, will either have to deal with both these issues or delegate these problems. Let’s write a test case for these operations:

struct implementation; 
int implementation_instances = 0; 
int implementation_copies_made = 0; 

void test_incomplete_implementation(const arg::grin_ptr<implementation>& const_grin) 
{ 
    assert(implementation_instances == 1); 
    assert(implementation_copies_made == 0);    
    { 
        // Check that copy construction works 
        // creates a new instance 

        arg::grin_ptr<implementation> nonconst_grin(const_grin); 

        assert(implementation_instances == 2); 
        assert(implementation_copies_made == 1); 

        // Check assignment calls copy constructor 
        // (and that the discarded implementation 
        // is deleted) 
        nonconst_grin = const_grin; 
        assert(implementation_instances == 2); 
        assert(implementation_copies_made == 2); 
    } 

    // Check destructor cleans up instances 
    assert(implementation_instances == 1); 
} 

Note that this test case is unable to initialise grin_ptr<> with an instance of implementation – this is a direct consequence of implementation being an incomplete type.

In implementing grin_ptr<> I make use of the fact that the constructor is instantiated in the implementation file for phone_list , where the class definition for implementation resides. Similarly calls to implementation member functions also require a complete type. These are tested in the next part of the test harness:

struct implementation 
{ 
    struct const_tag {}; 
    struct nonconst_tag {}; 

    const_tag function() const 
    { return const_tag(); } 

    nonconst_tag function() 
    { return nonconst_tag(); } 

    implementation() 
    { ++implementation_instances; } 

    implementation(const implementation&) 
    { ++implementation_instances; 
    ++implementation_copies_made; } 

    ~implementation() 
    { --implementation_instances; } 
}; 

void test_implementation() 
{ 
    assert(implementation_instances == 0); 
    assert(implementation_copies_made == 0); 

    { 
        arg::grin_ptr<implementation> grin(new implementation); 
        assert(implementation_instances == 1); 
        assert(implementation_copies_made == 0); 

        test_incomplete_implementation(grin); 

        // Check that copy construction works 
        // creates a new instance 
        const arg::grin_ptr<implementation>& const_grin(grin); 
        assert(implementation_instances == 1); 

        // if const qualification is wrong then 
        // these should produce compiler error 
        implementation::const_tag const_test = const_grin->function(); 
        implementation::nonconst_tag nonconst_test = grin->function(); 
    } 

    // Check destructor cleans up instance  
    assert(implementation_instances == 0); 
} 

If you examine the code for grin_ptr<> (shown in listing 1) you’ll see that each instance carries around a pointer to a structure containing function pointers do_copy and do_delete . This structure is initialised using a trick I first saw used by Richard Hickey [6]: the constructor (which is instantiated with a complete type) instantiates the corresponding my_delete_ftn and my_copy_ftn template functions and stores the pointers to them. Because these see the full definition of the class used to initialise the pointer they can make use of its copy constructor and destructor (the casts are there to support implementation hierarchies). Using pointers to functions provides a safe method for grin_ptr<> to copy and delete the object it owns. The point of passing around function pointers instead of the apparently more natural use of virtual member functions is that everything can be done “by value” and no dynamic allocation is required.

There are few features that can be seen in this implementation that are not immediately related to the foregoing discussion:

  • The dereference and indirection operators are const qualified, which allows the implementation class to overload on constin a natural manner.

  • The single argument (conversion) constructor may be initialised using a class derived from implementation – this allows different specialisations of implementation to be selected at runtime 1 . This functionality is illustrated in the test case given in Listing 2.

  • There is a two argument constructor that allows the user to provide custom copy and delete functionality.

Conclusion

The class template presented here - arg::grin_ptr<> ­removes some of the repetitive work required when implementing Cheshire Cat classes. In addition it is able to support applications that are considerably more sophisticated (making use of polymorphic implementations and/or overloading on const) than the phone_list example considered here.

Alan Griffiths
alan@octopull.demon.co.uk

References

  1. “shared_ptr class template”, Greg Colvin et al., http://www.boost.org/libs/smart_ptr/shared_ptr.htm

  2. “Ending with the grin”, Alan Griffiths, http://www.octopull.demon.co.uk/arglib/TheGrin.html

  3. ‘Interpreting “Supporting the “Cheshire Cat” idiom”’, Alan Griffiths & Allan Newton, Overload 38

  4. “Constructing bullet-proof classes”, John Carolan, Proceedings: C++ at Work ’89, SIGS

  5. Design Patterns, Gamma, Helm, Johnson, Vlissides ISBN 0-201­63361-2, Addison-Wesley

  6. Callbacks in C++ Using Template Functors, Richard Hickey C ++ Gems ISBN 1 884842 37 2

#ifndef INCLUDED_GRIN_PTR_HPP_ARG_20060308
#define INCLUDED_GRIN_PTR_HPP_ARG_20060308

#include <utility>
namespace arg
{
    template<typename Grin>
    class grin_ptr
    {
    public:
        /// Construct taking ownership of
        /// pointee.
        /// CheshireCat must be a complete type,
        /// Copyable and Destructable.

        template<typename CheshireCat>
        explicit grin_ptr(CheshireCat* pointee);

        /// Copy using copy function
        grin_ptr(const grin_ptr& rhs);

        /// Destroy using delete function
        ~grin_ptr() throw()
        {
            copy_delete->do_delete(p);
        }

        /// Return contents (const)
        const Grin* get() const
        {
            return p;
        }

        /// Return contents (non-const)
        Grin* get()
        {
            return p;
        }

        /// Dereference op (const)
        const Grin* operator->()
        const
        {
            return p;
        }

        /// Dereference op (non-const)
        Grin* operator->()
        {
            return p;
        }

        /// Dereference op (const)
        const Grin& operator*()
        const
        {
            return *p;
        }

        /// Dereference op (non-const)
        Grin& operator*()
        {
            return *p;
        }

        /// Swaps contents with "with"
        void swap(grin_ptr& with) throw();

        ///
        grin_ptr& operator=(const grin_ptr& rhs);

        /// Pointers to deep copy and delete
        /// functions
        struct copy_delete_ptrs
        {
            typedef void (*delete_ftn)(Grin*);
            typedef Grin* (*copy_ftn)(const Grin*);
            copy_ftn do_copy;
            delete_ftn do_delete;
        };

        /// Allow user to specify copy and delete
        grin_ptr(Grin* pointee, copy_delete_ptrs* cdp)
            : p(pointee), copy_delete(cdp) {}

    private:
        Grin* p;
        copy_delete_ptrs* copy_delete;

        template<typename CheshireCat>
        static void my_delete_ftn(Grin* p);

        template<typename CheshireCat>
        static Grin* my_copy_ftn(const Grin* p);
    };

    template<typename Grin>
    template<typename CheshireCat>
    inline void
    grin_ptr<Grin>::my_delete_ftn(Grin* p)
    {
        delete static_cast<CheshireCat*>(p);
    }

    template<typename Grin>
    template<typename CheshireCat>
    inline Grin*
    grin_ptr<Grin>::my_copy_ftn(const Grin* p)
    {
        return new CheshireCat(
                   static_cast<const CheshireCat&>(*p));
    }

    template<typename Grin>
    template<typename CheshireCat>
    inline grin_ptr<Grin>::grin_ptr(CheshireCat* pointee)
        : p(pointee), copy_delete(0)
    {
        void(sizeof(CheshireCat));
        static copy_delete_ptrs cdp =
        {
            &my_copy_ftn<CheshireCat>,
            &my_delete_ftn<CheshireCat>
        };
        copy_delete = &cdp;
    }

    template<typename Grin>
    inline void grin_ptr<Grin>::swap(grin_ptr& with) throw()
    {
        std::swap(p, with.p);
        std::swap(copy_delete, with.copy_delete);
    }

    template<typename Grin>
    inline grin_ptr<Grin>::grin_ptr(const grin_ptr& rhs)
        :
        p(rhs.copy_delete->do_copy(rhs.p)),
        copy_delete(rhs.copy_delete)
    {
    }

    template<typename Grin>
    inline grin_ptr<Grin>&
    grin_ptr<Grin>::operator=(const grin_ptr& rhs)
    {
        grin_ptr<Grin>(rhs).swap(*this);
        return *this;
    }
}

namespace std
{
    template<class Grin>
    inline void swap(
        ::arg::grin_ptr<Grin>& lhs,
        ::arg::grin_ptr<Grin>& rhs) throw()
    {
        lhs.swap(rhs);
    }
}
#endif
Listing 1 – grin_ptr.hpp
struct base
{
protected:
    base() {}
    base(const base&) {}
};

struct derived1 : base
{
    static int instances;

    derived1()  { ++instances; }    
    derived1(const derived1& src)
    : base(src) { ++instances; }
    ~derived1() { --instances; }
};

int derived1::instances = 0;

struct derived2 : base
{
    static int instances;

    derived2()  { ++instances; }
    derived2(const derived2& src)
    : base(src) { ++instances; }
    ~derived2() { --instances; }
};

int derived2::instances = 0;

void test_derived()
{
    assert(derived1::instances == 0);
    assert(derived2::instances == 0);

    {
        arg::grin_ptr<base> d1(new derived1);
        arg::grin_ptr<base> d2(new derived2);

        assert(derived1::instances == 1);
        assert(derived2::instances == 1);
        {
            // Check that copy works
            arg::grin_ptr<base> d(d1);
            assert(derived1::instances == 2);
            assert(derived2::instances == 1);

            // Check assignment from d1
            d = d1;
            assert(derived1::instances == 2);
            assert(derived2::instances == 1);

            // Check assignment from d2
            d = d2;
            assert(derived1::instances == 1);
            assert(derived2::instances == 2);
        }

        // Check destruction
        assert(derived1::instances == 1);
        assert(derived2::instances == 1);
    }

    // Check destruction
    assert(derived1::instances == 0);
    assert(derived2::instances == 0);
}
Listing 2 – test for polymorphic implementation

  1. This is a difference from the earlier implementation of grin_ptr – in that, if the user wanted to derive from implementation , it was necessary for implementation to derive from arg::cloneable and to declare the destructor virtual. I feel the current approach is more natural and extensible.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.