ABC interfaces, smart pointers and factory functions

Discussion in 'C++' started by Steven T. Hatton, Nov 30, 2006.

  1. I have a couple questions about the design pattern presented in the example
    quoted below. I can appreciate why the destructor is protected, but why is
    it not virtual? I am forced to assume that I am not expected to derive
    from X. But why impose such a restriction?

    Also, what are the tradeoffs between declaring createX() to be namespace
    local vs. being a static member function of X?

    <quote url="http://www.boost.org/libs/smart_ptr/sp_techniques.html">
    Using abstract classes for implementation hiding
    Another widely used C++ idiom for separating inteface and implementation is
    to use abstract base classes and factory functions. The abstract classes
    are sometimes called "interfaces" and the pattern is known
    as "interface-based programming". Again, shared_ptr can be used as the
    return type of the factory functions:
    // X.hpp:

    class X
    {
    public:

    virtual void f() = 0;
    virtual void g() = 0;

    protected:

    ~X() {}
    };

    shared_ptr<X> createX();

    -- X.cpp:

    class X_impl: public X
    {
    private:

    X_impl(X_impl const &);
    X_impl & operator=(X_impl const &);

    public:

    virtual void f()
    {
    // ...
    }

    virtual void g()
    {
    // ...
    }
    };

    shared_ptr<X> createX()
    {
    shared_ptr<X> px(new X_impl);
    return px;
    }

    A key property of shared_ptr is that the allocation, construction,
    deallocation, and destruction details are captured at the point of
    construction, inside the factory function. Note the protected and
    nonvirtual destructor in the example above. The client code cannot, and
    does not need to, delete a pointer to X; the shared_ptr<X> instance
    returned from createX will correctly call ~X_impl.
    </quote>

    --
    NOUN:1. Money or property bequeathed to another by will. 2. Something handed
    down from an ancestor or a predecessor or from the past: a legacy of
    religious freedom. ETYMOLOGY: MidE legacie, office of a deputy, from OF,
    from ML legatia, from L legare, to depute, bequeath. www.bartleby.com/61/
    Steven T. Hatton, Nov 30, 2006
    #1
    1. Advertising

  2. Steven T. Hatton

    Salt_Peter Guest

    Steven T. Hatton wrote:
    > I have a couple questions about the design pattern presented in the example
    > quoted below. I can appreciate why the destructor is protected, but why is
    > it not virtual? I am forced to assume that I am not expected to derive
    > from X. But why impose such a restriction?


    Boost's shared pointer will correctly invoke the derived ctor for you.
    No virtual d~tor required.
    If you need to know how it does that, read boost/checked_delete.hpp.

    As far as create() is concerned, that depends on your needs. Consider
    that a class might be a Factory with a create() static, public
    function, have X_impl with private ctor(s) only and make the Factory a
    friend of X_impl. It depends on the requirements.

    >
    > Also, what are the tradeoffs between declaring createX() to be namespace
    > local vs. being a static member function of X?
    >
    > <quote url="http://www.boost.org/libs/smart_ptr/sp_techniques.html">
    > Using abstract classes for implementation hiding
    > Another widely used C++ idiom for separating inteface and implementation is
    > to use abstract base classes and factory functions. The abstract classes
    > are sometimes called "interfaces" and the pattern is known
    > as "interface-based programming". Again, shared_ptr can be used as the
    > return type of the factory functions:
    > // X.hpp:
    >
    > class X
    > {
    > public:
    >
    > virtual void f() = 0;
    > virtual void g() = 0;
    >
    > protected:
    >
    > ~X() {}
    > };
    >
    > shared_ptr<X> createX();
    >
    > -- X.cpp:
    >
    > class X_impl: public X
    > {
    > private:
    >
    > X_impl(X_impl const &);
    > X_impl & operator=(X_impl const &);
    >
    > public:
    >
    > virtual void f()
    > {
    > // ...
    > }
    >
    > virtual void g()
    > {
    > // ...
    > }
    > };
    >
    > shared_ptr<X> createX()
    > {
    > shared_ptr<X> px(new X_impl);
    > return px;
    > }
    >
    > A key property of shared_ptr is that the allocation, construction,
    > deallocation, and destruction details are captured at the point of
    > construction, inside the factory function. Note the protected and
    > nonvirtual destructor in the example above. The client code cannot, and
    > does not need to, delete a pointer to X; the shared_ptr<X> instance
    > returned from createX will correctly call ~X_impl.
    > </quote>
    >


    #include <iostream>
    #include <boost/shared_ptr.hpp>

    class X
    {
    public:
    X() { std::cout << "X()\n"; }
    ~X() { std::cout << "~X()\n"; }
    };

    class X_impl : public X
    {
    public:
    X_impl() { std::cout << "X_impl()\n"; }
    ~X_impl() { std::cout << "~X_impl()\n"; }
    };

    int main()
    {
    boost::shared_ptr< X > sp_x(new X_impl);
    }

    /*
    X()
    X_impl()
    ~X_impl()
    ~X()
    */
    Salt_Peter, Nov 30, 2006
    #2
    1. Advertising

  3. Steven T. Hatton

    Kai-Uwe Bux Guest

    Steven T. Hatton wrote:

    > I have a couple questions about the design pattern presented in the
    > example
    > quoted below. I can appreciate why the destructor is protected, but why
    > is
    > it not virtual? I am forced to assume that I am not expected to derive
    > from X. But why impose such a restriction?


    Where did you get the idea that you are not supposed to derive from X? The
    whole point of the snippet is to demonstrate that you can derive from X and
    use a shared_ptr<X> as the return type of a factory. The class X_impl below
    derives from X.

    What may surprise you is this: shared_ptr<X> using the default deleter will
    call the correct destructor (~X_impl in the example) regardless of whether
    ~X it was declared virtual.


    > Also, what are the tradeoffs between declaring createX() to be namespace
    > local vs. being a static member function of X?


    Huh?

    > <quote url="http://www.boost.org/libs/smart_ptr/sp_techniques.html">
    > Using abstract classes for implementation hiding
    > Another widely used C++ idiom for separating inteface and implementation
    > is to use abstract base classes and factory functions. The abstract
    > classes are sometimes called "interfaces" and the pattern is known
    > as "interface-based programming". Again, shared_ptr can be used as the
    > return type of the factory functions:
    > // X.hpp:
    >
    > class X
    > {
    > public:
    >
    > virtual void f() = 0;
    > virtual void g() = 0;
    >
    > protected:
    >
    > ~X() {}
    > };
    >
    > shared_ptr<X> createX();
    >
    > -- X.cpp:
    >
    > class X_impl: public X
    > {
    > private:
    >
    > X_impl(X_impl const &);
    > X_impl & operator=(X_impl const &);
    >
    > public:
    >
    > virtual void f()
    > {
    > // ...
    > }
    >
    > virtual void g()
    > {
    > // ...
    > }
    > };
    >
    > shared_ptr<X> createX()
    > {
    > shared_ptr<X> px(new X_impl);
    > return px;
    > }
    >
    > A key property of shared_ptr is that the allocation, construction,
    > deallocation, and destruction details are captured at the point of
    > construction, inside the factory function. Note the protected and
    > nonvirtual destructor in the example above. The client code cannot, and
    > does not need to, delete a pointer to X; the shared_ptr<X> instance
    > returned from createX will correctly call ~X_impl.
    > </quote>



    Best

    Kai-Uwe Bux
    Kai-Uwe Bux, Nov 30, 2006
    #3
  4. Kai-Uwe Bux wrote:

    > Steven T. Hatton wrote:
    >
    >> I have a couple questions about the design pattern presented in the
    >> example
    >> quoted below. I can appreciate why the destructor is protected, but why
    >> is
    >> it not virtual? I am forced to assume that I am not expected to derive
    >> from X. But why impose such a restriction?

    >
    > Where did you get the idea that you are not supposed to derive from X? The
    > whole point of the snippet is to demonstrate that you can derive from X
    > and use a shared_ptr<X> as the return type of a factory. The class X_impl
    > below derives from X.


    The problem arrises when there are multiple levels of derivation, and one of
    the intermediate levels has something that needs to be destroyed. If the
    top base class destructor isn't virtual, none of the derived destructors
    will be called if/when its invoked directly.

    #include <iostream>
    struct VBase{
    virtual ~VBase(){ std::cerr << "VBase::~VBase" << std::endl; }
    };

    struct VDerived: public VBase {
    virtual ~VDerived(){ std::cerr << "VDerived::~VDerived" << std::endl; }
    };

    struct V: public VDerived{
    virtual ~V(){ std::cerr << "V::~V" << std::endl; }
    };

    struct Base{
    ~Base(){ std::cerr << "Base::~Base" << std::endl; }
    };

    struct Derived: public Base {
    ~Derived(){ std::cerr << "Derived::~Derived" << std::endl; }
    };

    struct B: public Derived{
    ~B(){ std::cerr << "B::~B" << std::endl; }
    };


    int main() {
    V* v_ptr(new V);
    B* b_ptr(new B);
    VBase* vbase_ptr(new V);
    Base* base_ptr(new B);

    std::cerr << "--------delete v_ptr--------" << std::endl;
    delete v_ptr;
    std::cerr << "--------delete b_ptr--------" << std::endl;
    delete b_ptr;
    std::cerr << "--------delete vbase_ptr--------" << std::endl;
    delete vbase_ptr;
    std::cerr << "--------delete base_ptr--------" << std::endl;
    delete base_ptr;
    }

    --------delete v_ptr--------
    V::~V
    VDerived::~VDerived
    VBase::~VBase
    --------delete b_ptr--------
    B::~B
    Derived::~Derived
    Base::~Base
    --------delete vbase_ptr--------
    V::~V
    VDerived::~VDerived
    VBase::~VBase
    --------delete base_ptr--------
    Base::~Base

    > What may surprise you is this: shared_ptr<X> using the default deleter
    > will call the correct destructor (~X_impl in the example) regardless of
    > whether ~X it was declared virtual.
    >
    >
    >> Also, what are the tradeoffs between declaring createX() to be namespace
    >> local vs. being a static member function of X?

    >
    > Huh?


    It's not uncommon in Java to have static member factory methods. It does
    have the advantage of being able to access private members of the
    constructed object without declaring a friend.

    --
    NOUN:1. Money or property bequeathed to another by will. 2. Something handed
    down from an ancestor or a predecessor or from the past: a legacy of
    religious freedom. ETYMOLOGY: MidE legacie, office of a deputy, from OF,
    from ML legatia, from L legare, to depute, bequeath. www.bartleby.com/61/
    Steven T. Hatton, Nov 30, 2006
    #4
  5. Salt_Peter wrote:

    >
    > Steven T. Hatton wrote:
    >> I have a couple questions about the design pattern presented in the
    >> example
    >> quoted below. I can appreciate why the destructor is protected, but why
    >> is
    >> it not virtual? I am forced to assume that I am not expected to derive
    >> from X. But why impose such a restriction?

    >
    > Boost's shared pointer will correctly invoke the derived ctor for you.
    > No virtual d~tor required.
    > If you need to know how it does that, read boost/checked_delete.hpp.


    So what are the consequences of making it virtual? Other than getting GCC
    to stop complaining, that is.

    > #include <iostream>
    > #include <boost/shared_ptr.hpp>
    >
    > class X
    > {
    > public:
    > X() { std::cout << "X()\n"; }
    > ~X() { std::cout << "~X()\n"; }
    > };
    >
    > class X_impl : public X
    > {
    > public:
    > X_impl() { std::cout << "X_impl()\n"; }
    > ~X_impl() { std::cout << "~X_impl()\n"; }
    > };
    >
    > int main()
    > {
    > boost::shared_ptr< X > sp_x(new X_impl);
    > }
    >
    > /*
    > X()
    > X_impl()
    > ~X_impl()
    > ~X()
    > */

    Hmmm....
    --
    NOUN:1. Money or property bequeathed to another by will. 2. Something handed
    down from an ancestor or a predecessor or from the past: a legacy of
    religious freedom. ETYMOLOGY: MidE legacie, office of a deputy, from OF,
    from ML legatia, from L legare, to depute, bequeath. www.bartleby.com/61/
    Steven T. Hatton, Nov 30, 2006
    #5
  6. Steven T. Hatton

    Kai-Uwe Bux Guest

    Steven T. Hatton wrote:

    > Kai-Uwe Bux wrote:
    >
    >> Steven T. Hatton wrote:
    >>
    >>> I have a couple questions about the design pattern presented in the
    >>> example
    >>> quoted below. I can appreciate why the destructor is protected, but why
    >>> is
    >>> it not virtual? I am forced to assume that I am not expected to derive
    >>> from X. But why impose such a restriction?

    >>
    >> Where did you get the idea that you are not supposed to derive from X?
    >> The whole point of the snippet is to demonstrate that you can derive from
    >> X and use a shared_ptr<X> as the return type of a factory. The class
    >> X_impl below derives from X.

    >
    > The problem arrises when there are multiple levels of derivation, and one
    > of
    > the intermediate levels has something that needs to be destroyed. If the
    > top base class destructor isn't virtual, none of the derived destructors
    > will be called if/when its invoked directly.
    >
    > #include <iostream>
    > struct VBase{
    > virtual ~VBase(){ std::cerr << "VBase::~VBase" << std::endl; }
    > };
    >
    > struct VDerived: public VBase {
    > virtual ~VDerived(){ std::cerr << "VDerived::~VDerived" << std::endl; }
    > };
    >
    > struct V: public VDerived{
    > virtual ~V(){ std::cerr << "V::~V" << std::endl; }
    > };
    >
    > struct Base{
    > ~Base(){ std::cerr << "Base::~Base" << std::endl; }
    > };
    >
    > struct Derived: public Base {
    > ~Derived(){ std::cerr << "Derived::~Derived" << std::endl; }
    > };
    >
    > struct B: public Derived{
    > ~B(){ std::cerr << "B::~B" << std::endl; }
    > };
    >
    >
    > int main() {
    > V* v_ptr(new V);
    > B* b_ptr(new B);
    > VBase* vbase_ptr(new V);
    > Base* base_ptr(new B);
    >
    > std::cerr << "--------delete v_ptr--------" << std::endl;
    > delete v_ptr;
    > std::cerr << "--------delete b_ptr--------" << std::endl;
    > delete b_ptr;
    > std::cerr << "--------delete vbase_ptr--------" << std::endl;
    > delete vbase_ptr;
    > std::cerr << "--------delete base_ptr--------" << std::endl;
    > delete base_ptr;
    > }
    >
    > --------delete v_ptr--------
    > V::~V
    > VDerived::~VDerived
    > VBase::~VBase
    > --------delete b_ptr--------
    > B::~B
    > Derived::~Derived
    > Base::~Base
    > --------delete vbase_ptr--------
    > V::~V
    > VDerived::~VDerived
    > VBase::~VBase
    > --------delete base_ptr--------
    > Base::~Base


    Apparently, you missed the point entirely: this is all about *shared_ptr<>*.
    Your example uses *raw pointers*. There is a difference. What you learned
    about pointers does not apply one-to-one to shared_ptr<>. The point of the
    snippet is to show that with shared_ptr the destructor does not need to be
    virtual. Try to do the above code with shared_ptr:

    #include <iostream>
    #include <tr1/memory>

    struct VBase{
    virtual ~VBase(){ std::cerr << "VBase::~VBase" << std::endl; }
    };

    struct VDerived: public VBase {
    virtual ~VDerived(){ std::cerr << "VDerived::~VDerived" << std::endl; }
    };

    struct V: public VDerived{
    virtual ~V(){ std::cerr << "V::~V" << std::endl; }
    };

    struct Base{
    ~Base(){ std::cerr << "Base::~Base" << std::endl; }
    };

    struct Derived: public Base {
    ~Derived(){ std::cerr << "Derived::~Derived" << std::endl; }
    };

    struct B: public Derived{
    ~B(){ std::cerr << "B::~B" << std::endl; }
    };


    template < typename T >
    void delete_shared ( std::tr1::shared_ptr<T> & s ) {
    s = std::tr1::shared_ptr<T>();
    }

    int main() {
    std::tr1::shared_ptr<V> v_ptr(new V);
    std::tr1::shared_ptr<B> b_ptr(new B);
    std::tr1::shared_ptr<VBase> vbase_ptr(new V);
    std::tr1::shared_ptr<Base> base_ptr(new B);

    std::cerr << "--------delete v_ptr--------" << std::endl;
    delete_shared( v_ptr );
    std::cerr << "--------delete b_ptr--------" << std::endl;
    delete_shared( b_ptr );
    std::cerr << "--------delete vbase_ptr--------" << std::endl;
    delete_shared( vbase_ptr );
    std::cerr << "--------delete base_ptr--------" << std::endl;
    delete_shared( base_ptr );
    }

    news_group> a.out
    --------delete v_ptr--------
    V::~V
    VDerived::~VDerived
    VBase::~VBase
    --------delete b_ptr--------
    B::~B
    Derived::~Derived
    Base::~Base
    --------delete vbase_ptr--------
    V::~V
    VDerived::~VDerived
    VBase::~VBase
    --------delete base_ptr--------
    B::~B
    Derived::~Derived
    Base::~Base

    Do you see now?


    >> What may surprise you is this: shared_ptr<X> using the default deleter
    >> will call the correct destructor (~X_impl in the example) regardless of
    >> whether ~X it was declared virtual.
    >>
    >>
    >>> Also, what are the tradeoffs between declaring createX() to be namespace
    >>> local vs. being a static member function of X?

    >>
    >> Huh?

    >
    > It's not uncommon in Java to have static member factory methods. It does
    > have the advantage of being able to access private members of the
    > constructed object without declaring a friend.


    You can do the same in C++.


    Best

    Kai-Uwe Bux
    Kai-Uwe Bux, Nov 30, 2006
    #6
  7. Steven T. Hatton

    Earl Purple Guest

    Kai-Uwe Bux quoted:
    >

    [ I put the quoted code in here rather than have "top-posting" ]
    > >
    > > class X
    > > {
    > > public:
    > >
    > > virtual void f() = 0;
    > > virtual void g() = 0;
    > >
    > > protected:
    > >
    > > ~X() {}
    > > };
    > >
    > > shared_ptr<X> createX();
    > >
    > > -- X.cpp:
    > >
    > > class X_impl: public X
    > > {
    > > private:
    > >
    > > X_impl(X_impl const &);
    > > X_impl & operator=(X_impl const &);
    > >
    > > public:
    > >
    > > virtual void f()
    > > {
    > > // ...
    > > }
    > >
    > > virtual void g()
    > > {
    > > // ...
    > > }
    > > };
    > >
    > > shared_ptr<X> createX()
    > > {
    > > shared_ptr<X> px(new X_impl);
    > > return px;
    > > }


    > Where did you get the idea that you are not supposed to derive from X? The
    > whole point of the snippet is to demonstrate that you can derive from X and
    > use a shared_ptr<X> as the return type of a factory. The class X_impl below
    > derives from X.
    >
    > What may surprise you is this: shared_ptr<X> using the default deleter will
    > call the correct destructor (~X_impl in the example) regardless of whether
    > ~X it was declared virtual.


    It surprises me. Are you sure? Are you sure the code will even compile?
    It might work with a custom deleter though, although even with that I'm
    not sure. However create first a shared_ptr< x_impl > then creating a
    shared_ptr< x > from it should work.

    > >
    > > A key property of shared_ptr is that the allocation, construction,
    > > deallocation, and destruction details are captured at the point of
    > > construction, inside the factory function. Note the protected and
    > > nonvirtual destructor in the example above. The client code cannot, and
    > > does not need to, delete a pointer to X; the shared_ptr<X> instance
    > > returned from createX will correctly call ~X_impl.


    The construction though would have to be shared_ptr< X_impl > so
    createX would work thus:

    shared_ptr< X > createX()
    {
    shared_ptr<X_impl> px(new X_impl); // creates the correct deleter
    return shared_ptr< X >( px ); // copy constructor copies the
    deleter from the original
    }

    This is at a point where X_impl is complete anyway (or you couldn't
    call new on it).
    Earl Purple, Nov 30, 2006
    #7
  8. Steven T. Hatton

    Kai-Uwe Bux Guest

    Earl Purple wrote:

    >
    > Kai-Uwe Bux quoted:
    >>

    > [ I put the quoted code in here rather than have "top-posting" ]
    >> >
    >> > class X
    >> > {
    >> > public:
    >> >
    >> > virtual void f() = 0;
    >> > virtual void g() = 0;
    >> >
    >> > protected:
    >> >
    >> > ~X() {}
    >> > };
    >> >
    >> > shared_ptr<X> createX();
    >> >
    >> > -- X.cpp:
    >> >
    >> > class X_impl: public X
    >> > {
    >> > private:
    >> >
    >> > X_impl(X_impl const &);
    >> > X_impl & operator=(X_impl const &);
    >> >
    >> > public:
    >> >
    >> > virtual void f()
    >> > {
    >> > // ...
    >> > }
    >> >
    >> > virtual void g()
    >> > {
    >> > // ...
    >> > }
    >> > };
    >> >
    >> > shared_ptr<X> createX()
    >> > {
    >> > shared_ptr<X> px(new X_impl);
    >> > return px;
    >> > }

    >
    >> Where did you get the idea that you are not supposed to derive from X?
    >> The whole point of the snippet is to demonstrate that you can derive from
    >> X and use a shared_ptr<X> as the return type of a factory. The class
    >> X_impl below derives from X.
    >>
    >> What may surprise you is this: shared_ptr<X> using the default deleter
    >> will call the correct destructor (~X_impl in the example) regardless of
    >> whether ~X it was declared virtual.

    >
    > It surprises me. Are you sure?


    Yes.

    > Are you sure the code will even compile?


    Yes.

    > It might work with a custom deleter though, although even with that I'm
    > not sure. However create first a shared_ptr< x_impl > then creating a
    > shared_ptr< x > from it should work.


    shared_ptr<T> has a templated constructor

    template < typename D >
    shared_ptr ( D * );

    this constructor initializes the deleter member so that ~D will be called.
    Thus, if you do:

    shared_ptr<T> t_ptr ( new D );

    the pointer will call ~D upon destruction.


    Try:

    #include <tr1/memory>
    #include <iostream>

    struct X {

    ~X ( void ) {
    std::cout << "~X\n";
    }

    };

    struct Y : public X {

    ~Y ( void ) {
    std::cout << "~Y\n";
    }

    };

    int main ( void ) {
    std::tr1::shared_ptr< X > x_ptr ( new Y );
    }


    Best

    Kai-Uwe Bux
    Kai-Uwe Bux, Nov 30, 2006
    #8
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Jiong Feng
    Replies:
    0
    Views:
    803
    Jiong Feng
    Nov 19, 2003
  2. Bruce Sam
    Replies:
    15
    Views:
    7,877
    John C. Bollinger
    Nov 19, 2004
  3. MotoK
    Replies:
    59
    Views:
    1,792
    Keith Thompson
    Sep 15, 2006
  4. vsgdp

    ABC inheriting from ABC

    vsgdp, Sep 24, 2005, in forum: C++
    Replies:
    1
    Views:
    302
    vsgdp
    Sep 24, 2005
  5. Gunter Henriksen

    x.abc vs x['abc']

    Gunter Henriksen, May 13, 2009, in forum: Python
    Replies:
    1
    Views:
    345
    alex23
    May 15, 2009
Loading...

Share This Page