473,385 Members | 1,326 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,385 software developers and data experts.

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<XcreateX();

-- 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<XcreateX()
{
shared_ptr<Xpx(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<Xinstance
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/
Nov 30 '06 #1
7 2029

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<XcreateX();

-- 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<XcreateX()
{
shared_ptr<Xpx(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<Xinstance
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()
*/

Nov 30 '06 #2
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<Xas the return type of a factory. The class X_impl below
derives from X.

What may surprise you is this: shared_ptr<Xusing 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<XcreateX();

-- 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<XcreateX()
{
shared_ptr<Xpx(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<Xinstance
returned from createX will correctly call ~X_impl.
</quote>

Best

Kai-Uwe Bux
Nov 30 '06 #3
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<Xas 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<Xusing 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/
Nov 30 '06 #4
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/
Nov 30 '06 #5
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<Xas 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<Vv_ptr(new V);
std::tr1::shared_ptr<Bb_ptr(new B);
std::tr1::shared_ptr<VBasevbase_ptr(new V);
std::tr1::shared_ptr<Basebase_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_groupa.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<Xusing 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
Nov 30 '06 #6

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<XcreateX();

-- 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<XcreateX()
{
shared_ptr<Xpx(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<Xas the return type of a factory. The class X_impl below
derives from X.

What may surprise you is this: shared_ptr<Xusing 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<Xinstance
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_implpx(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).

Nov 30 '06 #7
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<XcreateX();

-- 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<XcreateX()
{
shared_ptr<Xpx(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<Xas the return type of a factory. The class
X_impl below derives from X.

What may surprise you is this: shared_ptr<Xusing 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<Thas 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<Tt_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

Nov 30 '06 #8

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

14
by: David B. Held | last post by:
I wanted to post this proposal on c.l.c++.m, but my news server apparently does not support that group any more. I propose a new class of exception safety known as the "smart guarantee". ...
1
by: baylor | last post by:
In C#, an interface cannot mark any method as static. i'm told the ILASM supports it but i've never tested that Two questions. First, why? OK, i've heard the reason about interfaces being...
27
by: Susan Baker | last post by:
Hi, I'm just reading about smart pointers.. I have some existing C code that I would like to provide wrapper classes for. Specifically, I would like to provide wrappers for two stucts defined...
17
by: Picho | last post by:
Hi all, I popped up this question a while ago, and I thought it was worth checking again now... (maybe something has changed or something will change). I read this book about component...
4
by: Matthias Kaeppler | last post by:
Hi, I'm having a hard time figuring out how I can initialize a smart pointer based on a certain condition: if something then ptr = 0; // init with NULL else ptr = new XYZ; // init with a...
18
by: _dee | last post by:
Question about best use of interfaces: Say there's a 'Master' class that needs to implement a few interfaces: class Master : I1, I2, I3 { } The actual code already exists in smaller...
92
by: Jim Langston | last post by:
Someone made the statement in a newsgroup that most C++ programmers use smart pointers. His actual phrase was "most of us" but I really don't think that most C++ programmers use smart pointers,...
54
by: Boris | last post by:
I had a 3 hours meeting today with some fellow programmers that are partly not convinced about using smart pointers in C++. Their main concern is a possible performance impact. I've been explaining...
3
by: alebcn75 | last post by:
Hello, I'm having a design issue that I can't solve. I'm getting from a network a stream of data, which is a http message (my program is neither a client or server, it just catches messages over...
1
by: CloudSolutions | last post by:
Introduction: For many beginners and individual users, requiring a credit card and email registration may pose a barrier when starting to use cloud servers. However, some cloud server providers now...
0
by: ryjfgjl | last post by:
In our work, we often need to import Excel data into databases (such as MySQL, SQL Server, Oracle) for data analysis and processing. Usually, we use database tools like Navicat or the Excel import...
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
0
by: Charles Arthur | last post by:
How do i turn on java script on a villaon, callus and itel keypad mobile phone
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.