 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Andrei Alexandrescu (See Guest
|
Posted: Tue Feb 21, 2006 11:06 am Post subject: correctness & style question |
|
|
Imagine a design that pursues efficiency combined with flexibility. The
core is a set of transformation functions, which I'll model as taking
and returning integers.
I defined a base class like this:
struct Base {
virtual int Transform(int) = 0;
virtual ~Base() {}
};
Then I define a number of implementation of Base. At a point, I need to
cascade two such implementations, hence:
template <class First, class Second>
struct Combo : Base {
virtual int Transform(int x) {
return second_.Transform(first_.Transform(x));
}
private:
First first_;
Second second_;
};
I count on the fact that the compiler will be smart enough to figure out
that the two calls inside Combo::Transform aren't really virtuals. It
does. I can now combine objects in the Base hierarchy in interesting
ways, without big penalties in efficiency. Cool.
I also need to create objects from a factory. That is, a parameter is
read from the command line and a Base-derived object must be created
dynamically. Then the Combo trick doesn't work anymore; I'd need a Combo
that stores two pointers and delegates calls to them. Something like:
struct DynaCombo : Base {
// ctor etc.
virtual int Transform(int x) {
return second_->Transform(first_->Transform(x));
}
private:
First * first_;
Second * second_;
};
At this point I realize that DynaCombo could really reuse Combo's design
if only it used references:
struct DynaCombo : public Combo<Base &, Base &> {
// almost nothing to do!
};
Nicey. The problem is resource disposal: DynaCombo would have to hold
dynamically-allocated pointers (they come from a factory) and therefore
its destructor must delete those pointers. So here's a hack I came up with:
template <class First, class Second>
struct Combo : Base {
// ctor used by bona fide instantiations
Combo() {}
// ctor used by DynaCombo
Combo(First f, Second s) : first_(f), second_(s) {}
virtual int Transform(int x) {
return second_.Transform(first_.Transform(x));
}
protected:
First first_;
Second second_;
};
struct DynaCombo : public Combo<Base &, Base &> {
DynaCombo(Base * f, Base * s) :
Combo<Base &, Base &>(*f, *s) {}
~DynaCombo() {
// oy!
delete &first_;
delete &second_;
}
};
I have a few doubts about the validity and elegance of this design. For
a short quantum of time, the references first_ and second_ will pass (in
front of the eyes of Combo's and Base's destructors) not as two
triumphant comedians exiting the stage in thundering applause, but
instead as two burned corpses. I'm not sure how legal that is, and even
if it is, it's a fragile design as it relies on the destructors of Combo
and Base having empty bodies.
I could have Combo's destructor detect references and delete them, but I
also want to use Combo in other circumstances, when it's not supposed to
do that. I could also define some "smart references", but I'm looking
for a simple solution. So: (1) is the code legal as it is? (2) any ideas
for a better variant that maintains the advantage of simplicity?
Thanks,
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Howard Hinnant Guest
|
Posted: Wed Feb 22, 2006 12:06 pm Post subject: Re: correctness & style question |
|
|
In article <Iv0sKK.1upo (AT) beaver (DOT) cs.washington.edu>,
"Andrei Alexandrescu (See Website For Email)"
<SeeWebsiteForEmail (AT) moderncppdesign (DOT) com> wrote:
| Quote: | So here's a hack I came up with:
template <class First, class Second
struct Combo : Base {
// ctor used by bona fide instantiations
Combo() {}
// ctor used by DynaCombo
Combo(First f, Second s) : first_(f), second_(s) {}
virtual int Transform(int x) {
return second_.Transform(first_.Transform(x));
}
protected:
First first_;
Second second_;
};
struct DynaCombo : public Combo<Base &, Base &> {
DynaCombo(Base * f, Base * s) :
Combo<Base &, Base &>(*f, *s) {}
~DynaCombo() {
// oy!
delete &first_;
delete &second_;
}
};
I have a few doubts about the validity and elegance of this design. For
a short quantum of time, the references first_ and second_ will pass (in
front of the eyes of Combo's and Base's destructors) not as two
triumphant comedians exiting the stage in thundering applause, but
instead as two burned corpses. I'm not sure how legal that is, and even
if it is, it's a fragile design as it relies on the destructors of Combo
and Base having empty bodies.
I could have Combo's destructor detect references and delete them, but I
also want to use Combo in other circumstances, when it's not supposed to
do that. I could also define some "smart references", but I'm looking
for a simple solution. So: (1) is the code legal as it is? (2) any ideas
for a better variant that maintains the advantage of simplicity?
|
Speaking just for myself, I don't consider this a hack at all. Whenever
I have a template class in the pattern of your Base (e.g. pair, tuple,
compressed_pair, etc.) one of my regular questions to myself is: And
what if the template parameter is a reference type? Would that be
useful? What needs to be done to Base to facilitate usefulness?
The fact that ~Combo<First,Second>() will see references that point to
already destructed objects doesn't bother me a bit as long as ~Combo()
doesn't try to do anything with these members except implicitly destruct
them (and as long as you control the source code so that this behavior
can be maintained). The destructor for a reference is a no-op (if not
by the standard, then for a commercially viable compiler), so no worries
there. Base isn't even aware of first_ and second_, so it won't care
whether they're destructed or out partying.
The only complaint I have about this code is with Combo's constructor:
Combo(First f, Second s) : first_(f), second_(s) {}
It is overly inefficient for non-reference types, requiring up to two
copies instead of one. But fixing this is old hat for you and I'm
guessing you were just simplifying the demo by leaving that detail out.
For the benefit of others, and because I think this *is* the important
detail of such a design, in this situation you want:
1. If First is a non-reference type, the constructor parameter should
be (const First& f, ...).
2. If First is a reference type, the constructor parameter should be
(First f, ...)
(and similarly - and independently - for Second).
Note that const references are handled with equal ease by point 2.
Given something like Loki's Select (I think boost spells it mpl::if_c),
and given tr1, that ctor would look something like (untested):
Combo(
typename if_c
<
is_reference<First>::value,
First,
const First&
| Quote: | ::type f,
typename if_c |
<
is_reference<Second>::value,
Second,
const Second&
| Quote: | ::type s
) : first_(f), second_(s) {} |
Ok, one problem left, consider:
Combo<const Base&, whatever> c(Derived(), whatever);
In this case you're going to form a reference to a temporary that is
going away, and the compiler is going to let you get away with it! The
constructor needs to be augmented with an additional rule: If the
template parameter is a reference type (even a const reference type),
the associated argument to the constructor can not be an rvalue.
I know there's a way to do this in C++03, but quite frankly I don't have
the energy to figure it out right now. The easiest solution I see is
with two new language features:: rvalue reference and static_assert:
template <class F, class S>
Combo(F&& f, S&& s)
: first_(std::forward<F>(f)), second_(std::forward<S>(s))
{
static_assert(!is_reference<First>::value ||
is_lvalue_reference<F>::value,
"Bound temporary to reference");
static_assert(!is_reference<Second>::value ||
is_lvalue_reference<S>::value,
"Bound temporary to reference");
}
This (I believe) still satisfies the motivation behind our first two
requirements:
1. If First is a non-reference type, there will be only one copy of f
made while constructing first_.
2. If First is a reference type, it will bind directly to f with no
copies made.
Additionally, the static_asserts will catch the case when the template
parameter is a reference type and the ctor argument is an rvalue.
Prior to the introduction of rvalue references to your tool set, one
might consider just outlawing const references altogether (with a
static_assert substitute).
Finally, one additional caveat: If we were dealing with a converting
constructor (implicit, single parameter), then we would want to restrict
it (enable_if) such that is_convertible<F, First>. Doing so would mean
that is_convertible<X, Combo<T>> would continue to give the right answer
(true only if X is convertible to T).
Really, the bit about destructing references is the boring part of this
very interesting design! :-)
-Howard
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Carl Barron Guest
|
Posted: Wed Feb 22, 2006 12:06 pm Post subject: Re: correctness & style question |
|
|
In article <Iv0sKK.1upo (AT) beaver (DOT) cs.washington.edu>, See Website For
Email <SeeWebsiteForEmail (AT) moderncppdesign (DOT) com> wrote:
| Quote: | Nicey. The problem is resource disposal: DynaCombo would have to hold
dynamically-allocated pointers (they come from a factory) and therefore
its destructor must delete those pointers. So here's a hack I came up with:
template <class First, class Second
struct Combo : Base {
// ctor used by bona fide instantiations
Combo() {}
// ctor used by DynaCombo
Combo(First f, Second s) : first_(f), second_(s) {}
virtual int Transform(int x) {
return second_.Transform(first_.Transform(x));
}
protected:
First first_;
Second second_;
};
struct DynaCombo : public Combo<Base &, Base &> {
DynaCombo(Base * f, Base * s) :
Combo<Base &, Base &>(*f, *s) {}
~DynaCombo() {
// oy!
delete &first_;
delete &second_;
}
};
I have a few doubts about the validity and elegance of this design. For
a short quantum of time, the references first_ and second_ will pass (in
front of the eyes of Combo's and Base's destructors) not as two
triumphant comedians exiting the stage in thundering applause, but
instead as two burned corpses. I'm not sure how legal that is, and even
if it is, it's a fragile design as it relies on the destructors of Combo
and Base having empty bodies.
I could have Combo's destructor detect references and delete them, but I
also want to use Combo in other circumstances, when it's not supposed to
do that. I could also define some "smart references", but I'm looking
for a simple solution. So: (1) is the code legal as it is? (2) any ideas
for a better variant that maintains the advantage of simplicity?
why not hold the pointers in DynoCombo and delete them in its dtor? |
struct DynoCombo:Combo<Base &,Base&>
{
DynoCombo(Base *f,Base *s):p1(f),p2(s),ComboBase(*f,*s){}
~DynoCombo() { delete p1;delete p2;}
private:
Base *p1,*p2;
};
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
David Abrahams Guest
|
Posted: Wed Feb 22, 2006 12:06 pm Post subject: Re: correctness & style question |
|
|
"Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail (AT) moderncppdesign (DOT) com> writes:
| Quote: | I have a few doubts about the validity and elegance of this design. For
a short quantum of time, the references first_ and second_ will pass (in
front of the eyes of Combo's and Base's destructors) not as two
triumphant comedians exiting the stage in thundering applause, but
instead as two burned corpses. I'm not sure how legal that is, and even
if it is,
|
Base can't see 'em, and Combo doesn't touch 'em, so technically you're
safe.
| Quote: | it's a fragile design as it relies on the destructors of Combo
and Base having empty bodies.
|
Aren't all good destructors empty? ;-)
| Quote: | I could have Combo's destructor detect references and delete them, but I
also want to use Combo in other circumstances, when it's not supposed to
do that. I could also define some "smart references", but I'm looking
for a simple solution. So: (1) is the code legal as it is? (2) any ideas
for a better variant that maintains the advantage of simplicity?
|
I could be missing something, but it looks like this design is
suffering from OO-instinct and too much hierachy. It seems as though
relying on the compiler to remove virtuals is unnecessary, though I
might be missing something.
I would choose pointer syntax instead of reference syntax. Then you
can build a smart pointer that stores its pointee internally
(c.f. boost::optional -- though you wouldn't use it verbatim) and use
that for the Combo case that stores its referents. And you can manage
your resources more responsibly by passing around smart pointers.
Something like this:
template <class Ptr1, class Ptr2>
struct Combo
{
Combo() {}
Combo(Ptr1 p1, Ptr2 p2) : p1(p1), p2(p2) {}
int Transform(int x)
{ return p2->Transform( p1->Transform( x ) ); }
Ptr1 p1;
Ptr2 p2;
};
HTH,
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
werasm Guest
|
Posted: Wed Feb 22, 2006 12:06 pm Post subject: Re: correctness & style question |
|
|
Andrei Alexandrescu (See Website For Email) wrote:
| Quote: |
struct DynaCombo : public Combo<Base &, Base &> {
DynaCombo(Base * f, Base * s) :
Combo<Base &, Base &>(*f, *s) {}
~DynaCombo() {
// oy!
delete &first_;
delete &second_;
}
};
I have a few doubts about the validity and elegance of this design. For
a short quantum of time, the references first_ and second_ will pass (in
front of the eyes of Combo's and Base's destructors) not as two
triumphant comedians exiting the stage in thundering applause, but
instead as two burned corpses. I'm not sure how legal that is, and even
if it is, it's a fragile design as it relies on the destructors of Combo
and Base having empty bodies.
|
Conservative is best?
| Quote: |
I could have Combo's destructor detect references and delete them, but I
also want to use Combo in other circumstances, when it's not supposed to
do that. I could also define some "smart references", but I'm looking
for a simple solution. So: (1) is the code legal as it is? (2) any ideas
for a better variant that maintains the advantage of simplicity?
|
I have come up with this possible solution. There is always a slight
chance that you'd consider it viable...
Looking at your problem I thought the base_from_member idiom
would work nice, apart from the fact that it requires a non-abstract
type as member.
Inheriting from base_from_member first would have meant it would be
created before and destroyed after, which is what we want.
Here is what I came up with:
#include <memory>
#include <iostream>
//The transformation hierarchy...
struct Base
{
virtual int transform(int) = 0;
virtual ~Base() {}
};
struct Times10 : Base
{
virtual int transform(int v){ return v*10; }
virtual ~Times10() {}
};
struct Square : Base
{
virtual int transform(int v){ return v*v; }
virtual ~Square() {}
};
//Problem 1 - base_from_member not adequate due to fact that
// it requires non-abs type, therefore:
//Solution 1:
template <class T, int UniqueID = 0>
class MemberOwner
{
protected:
explicit MemberOwner( T* m ):m_( m ){ ; }
~MemberOwner(){ std::cout << "~MO" << std::endl; }
const std::auto_ptr<T> m_;
};
//Problem 2 - We need to delay the construction of Base inorder to
// make Ownership explicit. We cannot use a factory as this requires
// calling of concrete make functions, of which we do not
// know the names.
//Solution 2: Virtual delayed constructors...
template<class T>
struct DelayConstruct
{
virtual T* create() const = 0;
};
struct Times10Maker : DelayConstruct<Base>
{
virtual Times10* create() const{ return new Times10(); }
};
struct SquareMaker : DelayConstruct<Base>
{
virtual Square* create() const{ return new Square(); }
};
//The Combo classes mostly as presented by you...
template <class First, class Second>
struct Combo : Base
{
// ctor used by bona fide instantiations
Combo() {}
// ctor used by DynaCombo
Combo(First f, Second s) : first_(f), second_(s) {}
virtual int transform(int x)
{
return second_.transform(first_.transform(x));
}
protected:
First first_;
Second second_;
};
//...Finally, DynaCombo for mere mortals :-)
struct DynaComboT
{
typedef MemberOwner<Base,0> OwnerB1;
typedef MemberOwner<Base,1> OwnerB2;
};
struct DynaCombo
: public DynaComboT,
private DynaComboT::OwnerB1,
private DynaComboT::OwnerB2,
public Combo<Base &, Base &>
{
DynaCombo( const DelayConstruct<Base>& b1, const
DelayConstruct<Base>& b2 )
: OwnerB1( b1.create() ), OwnerB2( b2.create() ),
Combo<Base &, Base &>( *OwnerB1::m_, *OwnerB2::m_ )
{
}
~DynaCombo()
{
}
};
int main( int argc, char* argv[] )
{
Times10Maker a;
SquareMaker b;
DynaCombo dc( a, b );
std::cout << dc.transform( 10 ) << std::endl;
std::cin.get();
return 0;
}
The delayed construction forces ownership semantics and the
instantiator does not require type. The MemberOwner ensures
that object exists as long as required.
Hope I understood your problem correctly. The code did seem
to compile and run. Hope I did not leave anything out.
Kind regards,
Werner
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Dave Harris Guest
|
Posted: Wed Feb 22, 2006 12:06 pm Post subject: Re: correctness & style question |
|
|
SeeWebsiteForEmail (AT) moderncppdesign (DOT) com (Andrei Alexandrescu (See Website
For Email)) wrote (abridged):
| Quote: | I have a few doubts about the validity and elegance of this design. For
a short quantum of time, the references first_ and second_ will pass
(in front of the eyes of Combo's and Base's destructors) not as two
triumphant comedians exiting the stage in thundering applause, but
instead as two burned corpses. I'm not sure how legal that is, and even
if it is, it's a fragile design as it relies on the destructors of
Combo and Base having empty bodies.
|
I don't follow. Once the templates are instantiated, the code is like:
struct Combo__ : Base {
Combo__(Base &f, Base &s) : first_(f), second_(s) {}
// ...
protected:
Base &first_;
Base &second_;
};
struct DynaCombo : public Combo__ {
DynaCombo(Base * f, Base * s) :
Combo__(*f, *s) {}
~DynaCombo() {
// oy!
delete &first_;
delete &second_;
}
};
where Combo__ is the instantiation of Combo<Base&, Base&>. It seems to me
that nothing touches the objects referenced by first_ and second_ after
they have been deleted. Combo__ has references to them, but it doesn't use
them. I don't believe the implementation is allowed to, either (except as
raw bytes), else code like:
struct Demo {
int *p;
Demo() {
p = new int;
delete p;
}
~Demo() {
// Accessing p (or *p) not allowed here.
}
};
would be invalid, which it isn't.
In my view, your code is legal and does not depend on the destructors of
Combo and Base being empty. ~Base can contain anything it likes. ~Combo
can have code, provided it doesn't touch first_ or second_.
The restriction on ~Combo is a weak point. It at least warrants a comment
in the code, and perhaps a whole subclass, eg:
template <class First, class Second>
struct StaticCombo : public Combo<First, Second> {
StaticCombo(First &f, Second &s) :
Combo(f, s) {}
~StaticCombo() {
// first_ and second_ can be manipulated here,
// but not in ~Combo.
}
};
if there is something that needs to be done during Combo's destruction.
Thus Combo handles the transform stuff, and DynaCombo and StaticCombo
handle memory management.
-- Dave Harris, Nottingham, UK.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Peter Dimov Guest
|
Posted: Thu Feb 23, 2006 1:06 am Post subject: Re: correctness & style question |
|
|
Andrei Alexandrescu (See Website For Email) wrote:
| Quote: | Imagine a design that pursues efficiency combined with flexibility. The
core is a set of transformation functions, which I'll model as taking
and returning integers.
I defined a base class like this:
struct Base {
virtual int Transform(int) = 0;
virtual ~Base() {}
};
Then I define a number of implementation of Base. At a point, I need to
cascade two such implementations, hence:
template <class First, class Second
struct Combo : Base {
virtual int Transform(int x) {
return second_.Transform(first_.Transform(x));
}
private:
First first_;
Second second_;
};
|
[...]
You can define a function call_transform such that call_transform( t, x
) calls t.Transform( x ) when t has Base for a base class and
(*t).Transform( x ) otherwise. Then you can use call_transform inside
Combo::Transform and instantiate Combo with (smart) pointers or
iterators for First and Second.
It's even easier to use mem_fn( &Base::Transform ) but unfortunately
this won't eliminate the virtual dispatch (except possibly on KAI C++).
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
olegabr Guest
|
Posted: Thu Feb 23, 2006 1:06 am Post subject: Re: correctness & style question |
|
|
The simplest solution til now :)
#include <iostream>
struct Base {
virtual int Transform(int) = 0;
virtual ~Base() {}
};
template <class First, class Second>
struct Combo : Base {
// ctor used by bona fide instantiations
Combo() {}
// ctor used by DynaCombo
Combo(First f, Second s) : first_(f), second_(s) {}
virtual int Transform(int x) {
return second_.Transform(first_.Transform(x));
}
private:
First first_;
Second second_;
};
template <class First, class Second>
struct Combo<First*, Second*> : Base {
// ctor used by bona fide instantiations
Combo() {}
// ctor used by DynaCombo
Combo(First* f, Second* s) : first_(f), second_(s) {}
~Combo() {
delete first_;
delete second_;
}
virtual int Transform(int x) {
return second_->Transform(first_->Transform(x));
}
private:
First* first_;
Second* second_;
};
struct DynaCombo : public Combo<Base*, Base*> {
DynaCombo(Base * f, Base * s) :
Combo<Base*, Base*>(f, s) {}
};
struct Times10 : Base
{
virtual int Transform(int v){ return v*10; }
virtual ~Times10() {}
};
struct Square : Base
{
virtual int Transform(int v){ return v*v; }
virtual ~Square() {}
};
int main()
{
Base* a = new Times10();
Base* b = new Square();
DynaCombo dc( a, b );
std::cout << dc.Transform( 7 ) << std::endl;
return 0;
}
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
olegabr Guest
|
Posted: Thu Feb 23, 2006 1:06 am Post subject: Re: correctness & style question |
|
|
The simplest way to maintain simplicity, I believe, is to use "smart
references"
I can not see another way to express
1) custom deletion (for pointers and auto objects)
2) custom invocation syntax (for pointers and auto objects)
It can be done with some kind of Deletion policy and Invocation policy
for original Combo class
but it is not simpler then just factor out these policies to "smart
reference" class
moreover, it'll lead to more clean design
Hope it helps,
Oleg Abrosimov
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
johnchx2@yahoo.com Guest
|
Posted: Thu Feb 23, 2006 2:06 am Post subject: Re: correctness & style question |
|
|
Andrei Alexandrescu (See Website For Email) wrote:
| Quote: | I could have Combo's destructor detect references and delete them, but I
also want to use Combo in other circumstances, when it's not supposed to
do that.
|
Doesn't this sound like a job for a policy? As in...
struct NopDispose {
template <class T> void Dispose(T*) {}
};
struct DeleteDispose {
template <class T> void Dispose(T* pt) { delete pt; }
};
template < class T1, class T2, class Disposer = NopDispose >
struct Combo: private Disposer {
T1 m1;
T2 m2;
Combo(T1 a, T2 b): m1(a), m2(b) {}
~Combo() {
this->Dispose(&m1);
this->Dispose(&m2);
}
};
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Andrei Alexandrescu (See Guest
|
Posted: Thu Feb 23, 2006 2:06 am Post subject: Re: correctness & style question |
|
|
Carl Barron wrote:
| Quote: | why not hold the pointers in DynoCombo and delete them in its dtor?
struct DynoCombo:Combo<Base &,Base&
{
DynoCombo(Base *f,Base *s):p1(f),p2(s),ComboBase(*f,*s){}
~DynoCombo() { delete p1;delete p2;}
private:
Base *p1,*p2;
};
|
That frees me of the guilt of writing delete & blah, but is technically
quite the same thing ).
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Przemyslaw Szymanski Guest
|
Posted: Thu Feb 23, 2006 11:06 am Post subject: Re: correctness & style question |
|
|
Andrei Alexandrescu (See Website For Email) wrote:
| Quote: | Imagine a design that pursues efficiency combined with flexibility. The
core is a set of transformation functions, which I'll model as taking
and returning integers.
I defined a base class like this:
struct Base {
virtual int Transform(int) = 0;
virtual ~Base() {}
};
Then I define a number of implementation of Base. At a point, I need to
cascade two such implementations, hence:
template <class First, class Second
struct Combo : Base {
virtual int Transform(int x) {
return second_.Transform(first_.Transform(x));
}
private:
First first_;
Second second_;
};
I count on the fact that the compiler will be smart enough to figure out
that the two calls inside Combo::Transform aren't really virtuals. It
does. I can now combine objects in the Base hierarchy in interesting
ways, without big penalties in efficiency. Cool.
I also need to create objects from a factory. That is, a parameter is
read from the command line and a Base-derived object must be created
dynamically. Then the Combo trick doesn't work anymore; I'd need a Combo
that stores two pointers and delegates calls to them. Something like:
struct DynaCombo : Base {
// ctor etc.
virtual int Transform(int x) {
return second_->Transform(first_->Transform(x));
}
private:
First * first_;
Second * second_;
};
At this point I realize that DynaCombo could really reuse Combo's design
if only it used references:
struct DynaCombo : public Combo<Base &, Base &> {
// almost nothing to do!
};
Nicey. The problem is resource disposal: DynaCombo would have to hold
dynamically-allocated pointers (they come from a factory) and therefore
its destructor must delete those pointers. So here's a hack I came up with:
template <class First, class Second
struct Combo : Base {
// ctor used by bona fide instantiations
Combo() {}
// ctor used by DynaCombo
Combo(First f, Second s) : first_(f), second_(s) {}
virtual int Transform(int x) {
return second_.Transform(first_.Transform(x));
}
protected:
First first_;
Second second_;
};
struct DynaCombo : public Combo<Base &, Base &> {
DynaCombo(Base * f, Base * s) :
Combo<Base &, Base &>(*f, *s) {}
~DynaCombo() {
// oy!
delete &first_;
delete &second_;
}
};
I have a few doubts about the validity and elegance of this design. For
a short quantum of time, the references first_ and second_ will pass (in
front of the eyes of Combo's and Base's destructors) not as two
triumphant comedians exiting the stage in thundering applause, but
instead as two burned corpses. I'm not sure how legal that is, and even
if it is, it's a fragile design as it relies on the destructors of Combo
and Base having empty bodies.
I could have Combo's destructor detect references and delete them, but I
also want to use Combo in other circumstances, when it's not supposed to
do that. I could also define some "smart references", but I'm looking
for a simple solution. So: (1) is the code legal as it is? (2) any ideas
for a better variant that maintains the advantage of simplicity?
|
Hello,
I believe that this approach could be overly complicated. Besides, for
DynaCombo the calls to transform() would be virtual, through references
to Base. I thought about two solutions.
1. Using partial specialization of Combo
template < class First, class Second>
struct Combo<First*, Second*> : public Base
{
Combo(First* f, Second* s) : first(f), second(s) { }
~Combo()
{
delete first;
delete second;
}
int transform(int a) { return second->transform(first->transform(a));
}
First* first;
Second* second;
};
2. Unifying handling of non-pointer types and pointer ones in Combo
struct. This requires additional helper classes.
template <class T>
struct Dereference
{
T& operator()(T& p) { return p; }
};
template <class T>
struct Dereference<T*>
{
T& operator()(T* p) { return *p; }
};
template <class T>
struct Erase
{
void operator()(T& ) { }
};
template <class T>
struct Erase<T*>
{
void operator()(T* p) { delete p; }
};
template < class First, class Second>
struct Combo : public Base
{
Combo() { }
Combo(First f, Second s) : first(f), second(s) { }
int transform(int a)
{
return
Dereference<Second>()(second).transform(Dereference<First>()(first).transform(a));
}
~Combo()
{
Erase<First>()(first);
Erase<Second>()(second);
}
First first;
Second second;
};
In both cases following function would be useful (assuming that your
factory functions returns pointers to classes inherited from Base, not
the Base itself - virtual call):
template < class First, class Second>
Combo<First, Second> makeCombo(First f, Second s)
{
return Combo<First, Second>(f, s);
}
but in the latter case it allows mixing of pointer types with
non-pointer
ones.
Regards,
Przemek.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Carl Barron Guest
|
Posted: Thu Feb 23, 2006 11:06 am Post subject: Re: correctness & style question |
|
|
In article <Iv3t0p.22xH (AT) beaver (DOT) cs.washington.edu>, See Website For
Email <SeeWebsiteForEmail (AT) moderncppdesign (DOT) com> wrote:
| Quote: | Carl Barron wrote:
why not hold the pointers in DynoCombo and delete them in its dtor?
struct DynoCombo:Combo<Base &,Base&
{
DynoCombo(Base *f,Base *s):p1(f),p2(s),ComboBase(*f,*s){}
~DynoCombo() { delete p1;delete p2;}
private:
Base *p1,*p2;
};
That frees me of the guilt of writing delete & blah, but is technically
quite the same thing ).
I don't know for sure, but I tend to think is not the same thing, |
although I might be wrong.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Andrei Alexandrescu (See Guest
|
Posted: Thu Feb 23, 2006 11:06 am Post subject: Re: correctness & style question |
|
|
Howard Hinnant wrote:
| Quote: | In article <Iv0sKK.1upo (AT) beaver (DOT) cs.washington.edu>,
"Andrei Alexandrescu (See Website For Email)"
SeeWebsiteForEmail (AT) moderncppdesign (DOT) com> wrote:
I could have Combo's destructor detect references and delete them, but I
also want to use Combo in other circumstances, when it's not supposed to
do that. I could also define some "smart references", but I'm looking
for a simple solution. So: (1) is the code legal as it is? (2) any ideas
for a better variant that maintains the advantage of simplicity?
Speaking just for myself, I don't consider this a hack at all. Whenever
I have a template class in the pattern of your Base (e.g. pair, tuple,
compressed_pair, etc.) one of my regular questions to myself is: And
what if the template parameter is a reference type? Would that be
useful? What needs to be done to Base to facilitate usefulness?
|
I think that's a good way to approach thinking of such classes. I'll try
to appropriate it myself; honest, while designing Combo, I wasn't
thinking of references at all ).
| Quote: | The fact that ~Combo<First,Second>() will see references that point to
already destructed objects doesn't bother me a bit as long as ~Combo()
doesn't try to do anything with these members except implicitly destruct
them (and as long as you control the source code so that this behavior
can be maintained). The destructor for a reference is a no-op (if not
by the standard, then for a commercially viable compiler), so no worries
there. Base isn't even aware of first_ and second_, so it won't care
whether they're destructed or out partying.
|
(Yah, my mentioning of Base was a red herring.) Your mentionin of "by
the standard" is what worries me a bit - would the compiler be allowed
at least in theory do anything with references during their destruction?
| Quote: | The only complaint I have about this code is with Combo's constructor:
Combo(First f, Second s) : first_(f), second_(s) {}
It is overly inefficient for non-reference types, requiring up to two
copies instead of one. But fixing this is old hat for you and I'm
guessing you were just simplifying the demo by leaving that detail out.
For the benefit of others, and because I think this *is* the important
detail of such a design, in this situation you want:
1. If First is a non-reference type, the constructor parameter should
be (const First& f, ...).
2. If First is a reference type, the constructor parameter should be
(First f, ...)
(and similarly - and independently - for Second).
Note that const references are handled with equal ease by point 2.
Given something like Loki's Select (I think boost spells it mpl::if_c),
and given tr1, that ctor would look something like (untested):
[snip] |
Thanks for the walk through techniques of dealing with this glaring
problem. It's quite a pity C++03 doesn't offer a simple solution. As of
my Combo design, it solves it in an unexpectedly simple (albeit limited)
way: the objects in the hierarchy are not copyable, so that constructor
call would *only* compile for references. Cute, eh? )
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Andrei Alexandrescu (See Guest
|
Posted: Thu Feb 23, 2006 11:06 am Post subject: Re: correctness & style question |
|
|
Peter Dimov wrote:
| Quote: | Andrei Alexandrescu (See Website For Email) wrote:
Imagine a design that pursues efficiency combined with flexibility. The
core is a set of transformation functions, which I'll model as taking
and returning integers.
I defined a base class like this:
struct Base {
virtual int Transform(int) = 0;
virtual ~Base() {}
};
Then I define a number of implementation of Base. At a point, I need to
cascade two such implementations, hence:
template <class First, class Second
struct Combo : Base {
virtual int Transform(int x) {
return second_.Transform(first_.Transform(x));
}
private:
First first_;
Second second_;
};
[...]
You can define a function call_transform such that call_transform( t, x
) calls t.Transform( x ) when t has Base for a base class and
(*t).Transform( x ) otherwise. Then you can use call_transform inside
Combo::Transform and instantiate Combo with (smart) pointers or
iterators for First and Second.
|
That would work if it weren't for the detail that I've hidden for the
sake of simplification: there are many more functions in addition to
Transform.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|