Velocity Reviews

Velocity Reviews (http://www.velocityreviews.com/forums/index.php)
-   C++ (http://www.velocityreviews.com/forums/f39-c.html)
-   -   ABC interfaces, smart pointers and factory functions (http://www.velocityreviews.com/forums/t458843-abc-interfaces-smart-pointers-and-factory-functions.html)

Steven T. Hatton 11-30-2006 05:43 AM

ABC interfaces, smart pointers and factory functions
 
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/

Salt_Peter 11-30-2006 06:32 AM

Re: ABC interfaces, smart pointers and factory functions
 

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()
*/


Kai-Uwe Bux 11-30-2006 06:34 AM

Re: ABC interfaces, smart pointers and factory functions
 
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

Steven T. Hatton 11-30-2006 07:56 AM

Re: ABC interfaces, smart pointers and factory functions
 
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 11-30-2006 08:09 AM

Re: ABC interfaces, smart pointers and factory functions
 
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/

Kai-Uwe Bux 11-30-2006 08:28 AM

Re: ABC interfaces, smart pointers and factory functions
 
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

Earl Purple 11-30-2006 10:20 AM

Re: ABC interfaces, smart pointers and factory functions
 

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).


Kai-Uwe Bux 11-30-2006 10:45 AM

Re: ABC interfaces, smart pointers and factory functions
 
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



All times are GMT. The time now is 12:47 PM.

Powered by vBulletin®. Copyright ©2000 - 2014, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.