How to get recursion in constructor?

170 views
Skip to first unread message

Krzysztof Żelechowski

unread,
Aug 31, 2005, 10:54:01 AM8/31/05
to
My dear expert friends,

How can I correct the following code to make it compile under C++? Plz help
coz I'm stuck.

Chris

class X {

int const m;
public:
X(int);

X(int a, int b): X(a + b) {

// do the same thing as X(a + b) does without knowing what it X(int) does

}

};


I can think of solving this problem by splitting the class into two:

class Xb {
public:
int const m;
Xb(int);
};

class X: public Xb {
X(int a): Xb(a) {
}
X(int a, int b): Xb(a + b) {
}
};

However, this is very clumsy. On the other hand, the named constructor
idiom allows recursion but my class is a template argument and I cannot use
the named constructor in the template code. And there is no way back: you
cannot get X::X(int, int) from static X X::create(int, int). Or can you?


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Jens Seidel

unread,
Sep 1, 2005, 5:17:04 AM9/1/05
to
Krzysztof =AFelechowski wrote:

> class X {
>
> int const m;
> public:
> X(int);
>
> X(int a, int b): X(a + b) {
>

> // do the same thing as X(a + b) does without knowing what it X(int) do=
es

This is valid in C# but not in C++.

> I can think of solving this problem by splitting the class into two:
>

> However, this is very clumsy.

Right.

Why not just using an init function?
private:
void init(int a, int b=0)

and calling init() from X(int) resp. X(int, int)?

Jens

Rob

unread,
Sep 1, 2005, 5:19:52 AM9/1/05
to
Krzysztof /elechowski wrote:

Two alternatives I can think of;

First is to use a helper function;

class X
{
public:
X(int a) {initalise(a);};
X(int a, int b) {initialise(a+b);};
private:
void initialise(int);
};

The second is to use default arguments;

class X
{
public:
X(int a, int b = 0)
{
// initialise in terms of a+b
};
};

Kelly Walker

unread,
Sep 1, 2005, 5:24:49 AM9/1/05
to

"Krzysztof Żelechowski" <kri...@qed.pl> wrote in message
news:df456i$l6r$1...@node1.news.atman.pl...

> My dear expert friends,
>
> How can I correct the following code to make it compile under C++? Plz
> help
> coz I'm stuck.
>
> Chris
>
>
>
> class X {
>
> int const m;
> public:
> X(int);
>
> X(int a, int b): X(a + b) {
>
> // do the same thing as X(a + b) does without knowing what it X(int) does
>
> }
>
> };
>
>
> I can think of solving this problem by splitting the class into two:
>
> class Xb {
> public:
> int const m;
> Xb(int);
> };
>
> class X: public Xb {
> X(int a): Xb(a) {
> }
> X(int a, int b): Xb(a + b) {
> }
> };
>
> However, this is very clumsy. On the other hand, the named constructor
> idiom allows recursion but my class is a template argument and I cannot
> use
> the named constructor in the template code. And there is no way back: you
> cannot get X::X(int, int) from static X X::create(int, int). Or can you?
>
>

This may only satisfy your simplified example, but what about using a
private initialization function as in:

class X {
public:
X(int a) {

init(a);
}

X(int a, int b) {
init(a + b);
}

private:
init(int i) {...}
};

-Kelly

Tony Delroy

unread,
Sep 1, 2005, 7:17:05 AM9/1/05
to
Another way is:

X::X(int a, int b) { *this = X(a + b); }

Tony

Tony Delroy

unread,
Sep 2, 2005, 7:07:56 AM9/2/05
to
Oh, BTW: your topic title "recursion in constructors" is a misnomer.
Calling one overloaded function from another isn't recursion.

Plus, something I didn't bother to mention earlier... the assignment
technique can only be used in the function body, so any default
construction done in the initialiser list may be wasted, and indeed
need to be torn down before the new values can be copy-constructed.
Still, an optimiser may be able to remove this overhead.

Christopher Yeleighton

unread,
Sep 2, 2005, 7:03:51 AM9/2/05
to

"Jens Seidel" <Jens....@imkf.tu-freiberg.de> wrote in message
news:df65ug$2tef$1...@nserver.hrz.tu-freiberg.de...

> Krzysztof =AFelechowski wrote:
>
>> class X {
>>
>> int const m;
>> public:
>> X(int);
>>
>> X(int a, int b): X(a + b) {
>>
>> // do the same thing as X(a + b) does without knowing what it X(int) do=
> es
>
> This is valid in C# but not in C++.
>
>> I can think of solving this problem by splitting the class into two:
>>
>> However, this is very clumsy.
>
> Right.
>
> Why not just using an init function?
> private:
> void init(int a, int b=0)
>
> and calling init() from X(int) resp. X(int, int)?
>
> Jens

Won't do coz m is const.
Chris

Christopher Yeleighton

unread,
Sep 2, 2005, 7:11:00 AM9/2/05
to

"Tony Delroy" <tony_i...@yahoo.co.uk> wrote in message
news:1125568114.9...@o13g2000cwo.googlegroups.com...

> Another way is:
>
> X::X(int a, int b) { *this = X(a + b); }
>
> Tony
>

Won't do coz m is const.
Chris

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Christopher Yeleighton

unread,
Sep 2, 2005, 7:11:42 AM9/2/05
to

"Rob" <nos...@nonexistant.com> wrote in message
news:4316...@duster.adelaide.on.net...

Won't do coz m is const.
Chris

> The second is to use default arguments;


>
> class X
> {
> public:
> X(int a, int b = 0)
> {
> // initialise in terms of a+b
> };
> };
>

Won't do if X(int a): m(a + 01) {}
Thanx for your time
Chris

Christopher Yeleighton

unread,
Sep 2, 2005, 7:10:15 AM9/2/05
to

"Kelly Walker" <kelly_g...@earthlink.net> wrote in message
news:FKrRe.5198$FW1...@newsread3.news.atl.earthlink.net...


Won't do coz m is const. Does this ultimately mean that keeping references
as class members is a no-no?
Chris

j_ri...@gmx.de

unread,
Sep 2, 2005, 10:30:11 AM9/2/05
to
Krzysztof Żelechowski schrieb:

> class X {
>
> int const m;
> public:
> X(int);
>
> X(int a, int b): X(a + b) {
> // do the same thing as X(a + b) does without knowing what it X(int) does
> }
> };

You can write a separate function which returns your m:

class X
{
int const m;

static int calculate( int );

public:
X( int a )
: m( calculate( a ) )
{}

X( int a, int b )
: m( calculate( a + b ) )
{}
};


Jörg

Gene Bushuyev

unread,
Sep 2, 2005, 12:39:30 PM9/2/05
to
"Krzysztof Żelechowski" <kri...@qed.pl> wrote in message
news:df456i$l6r$1...@node1.news.atman.pl...
> My dear expert friends,
>
> How can I correct the following code to make it compile under C++? Plz help
> coz I'm stuck.
>
> Chris
>
>
>
> class X {
>
> int const m;
> public:
> X(int);
>
> X(int a, int b): X(a + b) {
>
> // do the same thing as X(a + b) does without knowing what it X(int) does
>
> }
>
> };

There is no problem, or better say the problem is contrived. There is no need in
second constructor, the clients should call X(a+b). When you decide that one
ctor needs to call another, stop and think which constructor is actually needed,
you definitely don't need both.
Having said that, it is possible that two legitimate constructors need to share
the same functionality. In such a case, the class is a candidate for splitting
to two, as there is obviously some non-trivial functionality that deserves to be
in a separate base class.

- gene

P.S. "init" functions are evil, as well as calling copy assignment in ctor.

Tony Delroy

unread,
Sep 2, 2005, 5:48:46 PM9/2/05
to
Ahhh... I'd overlooked that detail. Well, you have to choose between
using amenable data types (e.g. not references, not constants) that can
be set after the initialiser list, and the inconvenience of explicitly
writing each constructor. - Tony

Ali Çehreli

unread,
Sep 3, 2005, 5:28:21 AM9/3/05
to
"Tony Delroy" <tony_i...@yahoo.co.uk> wrote in message
news:1125568114.9...@o13g2000cwo.googlegroups.com...
> Another way is:
>
> X::X(int a, int b) { *this = X(a + b); }

Won't work.

The object on the left hand side of operator= is not constructed at the time
you call the assignment. Yes, some or all of its members are default
initialized, but the object is not.

By definition, the object on the left hand side will be constructed when the
constructor completes.

Ali

Martin Stettner

unread,
Sep 3, 2005, 2:52:22 PM9/3/05
to

"Christopher Yeleighton" <kri...@qed.pl> schrieb im Newsbeitrag
news:df71qn$o4b$1...@sklad.atcom.net.pl...

>
> "Jens Seidel" <Jens....@imkf.tu-freiberg.de> wrote in message
> news:df65ug$2tef$1...@nserver.hrz.tu-freiberg.de...
>> Krzysztof =AFelechowski wrote:
>>
>>> class X {
>>>
>>> int const m;
>>> public:
>>> X(int);
>>>
>>> X(int a, int b): X(a + b) {
>>>
>>> // do the same thing as X(a + b) does without knowing what it X(int) do=
>> es
>>
>> This is valid in C# but not in C++.
>>
>>> I can think of solving this problem by splitting the class into two:
>>>
>>> However, this is very clumsy.
>>
>> [snip]

>
> Won't do coz m is const.
> Chris

Perhaps you can write a function that computes the value of m from given the
argument:

protected:
int computeM(int);

(or even static int computeM(int);)

Then write:

X(int a) : m(computeM(a)) { /* ... */ }
X(int a, int b): m(computeM(a,b)) { /* ... */ }

greetings
Martin

vadim alexandrov

unread,
Sep 3, 2005, 11:24:10 PM9/3/05
to
Probably what you are asking for is:

X::X(int a, int b ) {

this->~X(); // destroy itself
new (this) X( a+b ); // placement new
}

But it doesn't look nice.
You can ommit calling destructor first, if members are primitive types
only.

(Sorry, I don't have C++ compiler here to check this code fully)

Vadim Alexandrov

Marc Mutz

unread,
Sep 3, 2005, 11:37:50 PM9/3/05
to
Gene Bushuyev wrote:
<snip>
> P.S. "init" functions are evil
<snip>

It might be that I'm born yesterday again, but: "Hu?"
Care to explain?

Thanks,
Marc

Frank Chang

unread,
Sep 5, 2005, 7:15:33 AM9/5/05
to
Vadim, I should add the original poster said that the constructor X(int
a) was a "black box" and we need not know what it does. But suppose the
constructor X(int a) opens a socket or a database connection, then we
could not utilize a temporary object created by the X(int a)
constructor, as shown in my previous post because it would be
immedately destroyed after the const member initialization:

X(int a, int b)
:m(X(a+b).m)
{}

So I am changing it to

X(int a, int b )

:m((new X(a+b))->m),
{}


so that resources allocated in the X(int a) constructor could be closed
or deallocated in the destructor ~X() when X goes out of scope or is
deleted.

The class X nows looks like:
class X
{
private:
int const m;
public:
X(int a);

X(int a, int b )

:m((new X(a+b))->m)
{}

~X();
};

Thank you.


class X
{
private:
int const m;
public:
X(int a);

X(int a, int b )

:m((new X(a+b))->m),
{}

~X();
};

Frank Chang

unread,
Sep 5, 2005, 7:20:31 AM9/5/05
to
Vadim, Your suggested code,

X::X(int a, int b ) {
this->~X(); // destroy itself
new (this) X( a+b ); // placement new
}

does not compile. The error message I get is: 'X::m' : must be
initialized in constructor base/member initializer list. In the ARM ,
Margaret Ellis and Bjorne Stroustrup state that the const member
variables , such as X::m, can only be initialized in the member
initialization list:
However, your idea is still very good if we change it to:

class X
{
private:
int const m;
public:
X(int a)

:m(a)
{}

X::X(int a, int b )

:m(X(a+b).m)
{}
};

Thank you.

msalters

unread,
Sep 5, 2005, 7:44:13 AM9/5/05
to

Marc Mutz schreef:

> Gene Bushuyev wrote:
> <snip>
> > P.S. "init" functions are evil
> <snip>
>
> It might be that I'm born yesterday again, but: "Hu?"
> Care to explain?

/Public/ init functions that /must/ be called are evil - they
should be replaced by constructors that actually construct.

HTH,
Michiel Salters

Marc Mutz

unread,
Sep 5, 2005, 7:40:43 AM9/5/05
to
Forget this suggestion immediately :)
Don't even look at it.

vadim alexandrov wrote:
<snip>


> Probably what you are asking for is:
>
> X::X(int a, int b ) {
> this->~X(); // destroy itself
> new (this) X( a+b ); // placement new
> }
>
> But it doesn't look nice.
> You can ommit calling destructor first, if members are
> primitive types only.
>
> (Sorry, I don't have C++ compiler here to check this
> code fully)

<snip>

Ok you did nonetheless. What's going on here is that this
code invoked the dtor on a non-initialized object.
Initialization of an object is successful only after the
ctor returns sucessfully. Only then may you call the dtor
on the object.

Marc

vadim alexandrov

unread,
Sep 6, 2005, 6:50:46 AM9/6/05
to
class X
{
private:
int const m;
public:
X(int a);
X(int a, int b )
:m((new X(a+b))->m), // memory leak here
{}
~X();
};

consider:

static char sTmpX[ sizeof( X ) ];

class X {
private:
int const m;
public:
X(int a);
X(int a, int b )

:m( (new(sTmpX) X(a+b))->m ), // should work
{}
~X();
};

It is not nice.
It doesn't call destructor on sTmpX.
It is not thread-safe.

Think of class redesign.

Frank Chang

unread,
Sep 6, 2005, 9:00:40 PM9/6/05
to
Vadim, I posted this answer first on Sept 5, 2005. Perhaps you didn't
see it:

class X
{
private:
int const m;
public:
X(int a);

X(int a, int b )

:m(X(a+b).m) // no memory leak but unnamed temporary object X
{}

~X();

};


Thank you.

werasmus

unread,
Sep 6, 2005, 9:09:39 PM9/6/05
to

vadim alexandrov wrote:
> consider:
>
> static char sTmpX[ sizeof( X ) ];
>
> class X {
> private:
> int const m;
> public:
> X(int a);
> X(int a, int b )
> :m( (new(sTmpX) X(a+b))->m ), // should work
> {}
> ~X();
> };

How about:

class X
{
public:
X(int a): m(a){ ; }
X(int a, int b)
: m(makeFrom(X(a+b)).m)
{
}

private:
//Default required for X instantiated in makeFrom
X():m(0){ ; }

static X const& makeFrom(X const& other)
{
//Will only be instantiated the first time makeFrom is called,
// right?
static X x;
x.m = other.m;

// or... x = other;
}

int const m;
};

Frank Chang

unread,
Sep 7, 2005, 10:04:49 AM9/7/05
to
werasmus, I get compilation errors when I try to compile your version
of class X: 1) X: no appropriate default constructor available 2)
l-value requires const object. Also, I don't see a return ??? in your
static makeFrom(X const& other) function. Thank you.

werasmus

unread,
Sep 7, 2005, 3:09:55 PM9/7/05
to

werasmus wrote:
> How about:
>
> class X
> {
> public:
> X(int a): m(a){ ; }
> X(int a, int b)
> : m(makeFrom(X(a+b)).m)
> {
> }
>
> private:
> //Default required for X instantiated in makeFrom
> X():m(0){ ; }
>
> static X const& makeFrom(X const& other)
> {
> //Will only be instantiated the first time makeFrom is called,
> // right?
> static X x;
> x.m = other.m;
>
> // or... x = other;
> }
>
> int const m;
> };

Sorry, somehow missed the int <const> m... bad. Then above would not
work, of course (besides not being thread safe). Looking at the
original problem, why can't the instantiator not just do:

X x(1+1);

This then calls the only constructor, which BTW could be explicit. Or
how about having:

class X
{ //... includes int const m et. all.
public:
X& operator+=( const X& rhs );
};
const X operator+( const X& lhs, const X& rhs )
{
return( X(lhs) += rhs );
}

Now we could instantiate X as:

X x( X(10)+X(20) );

For this example though, why the bother:

X x(1+1)...
... still rules

Regards,

Werner

Frank Chang

unread,
Sep 8, 2005, 5:13:59 AM9/8/05
to
Werner, I've certainly made my share of mistakes on this bulletin
board. I understand that many people making posts do not have a
compiler with them when they make their posts. About your idea,

class X
{ //... includes int const m et. all.
public:
X& operator+=( const X& rhs );
};


const X operator+( const X& lhs, const X& rhs )
{
return( X(lhs) += rhs );
}

Now we could instantiate X as:

X x( X(10)+X(20) );


This will run OK as long as the X::X(int a) constructor follows the
invariant :
X(a + b) = X(a) + X(b) . But , the original poster told we should make
no assumptions about what the constructor X(int a) does. Thank you.

Christopher Conrade Zseleghovski

unread,
Sep 8, 2005, 5:20:17 AM9/8/05
to
werasmus <w_e...@telkomsa.net> wrote:
>
> Sorry, somehow missed the int <const> m... bad. Then above would not
> work, of course (besides not being thread safe). Looking at the
> original problem, why can't the instantiator not just do:
>
> X x(1+1);
>
> This then calls the only constructor, which BTW could be explicit. Or
> how about having:
>
> class X
> { //... includes int const m et. all.
> public:
> X& operator+=( const X& rhs );
> };
> const X operator+( const X& lhs, const X& rhs )
> {
> return( X(lhs) += rhs );

Error: no constructor available for X(lhs)
Error: a temporary cannot be used where a reference is needed

Christopher Conrade Zseleghovski

unread,
Sep 8, 2005, 5:21:27 AM9/8/05
to
werasmus <w_e...@telkomsa.net> wrote:
>
> How about:
>
> class X
> {
> public:
> X(int a): m(a){ ; }
> X(int a, int b)
> : m(makeFrom(X(a+b)).m)
> {
> }
>
> private:
> //Default required for X instantiated in makeFrom
> X():m(0){ ; }
>
> static X const& makeFrom(X const& other)
> {
> //Will only be instantiated the first time makeFrom is called,
> // right?
> static X x;
> x.m = other.m;

Error: X::m is constant

werasmus

unread,
Sep 9, 2005, 11:25:56 AM9/9/05
to

Frank Chang wrote:
> Werner, I've certainly made my share of mistakes on this bulletin
> board. I understand that many people making posts do not have a
> compiler with them when they make their posts.

Yes, I did not explicitly compile the code. I forgot that when you have
a const member, the default assignment operator is not adequate. The
default copy constructor does seem adequate, though. The 2nd solution I
have given would also fail:
class X{ //...


X& operator+=( const X& rhs );
};

is not possible as <X& operator=> would fail. Therefore operator+ could
not be implemented in terms of operator+=. Operator+ can be implemented
by doing this though:

X operator( const X& lhs, const X& rhs)
{
return X( lhs.getm()+rhs.getm() );
}

> This will run OK as long as the X::X(int a) constructor follows the
> invariant :
> X(a + b) = X(a) + X(b).

Yes, agree:

X( int a ):m_( 1000*a){ ; } //I made assumptions...you win again
X( int a, int b ): m_( X(a+b).m_{ ; } //Good idea ;-), Thanks

Once again, I'm writing without compiling (not entirely). Pardon any
mistakes. I think we are now in agreement. The not being able to assign
when a const member exists have caught me more than once in the past.

Kind regards (Appreciate for your patience/pardon),

Werner

Tony Delroy

unread,
Sep 9, 2005, 8:33:18 PM9/9/05
to
The object on the left hand side has passed the
default-initialisation/initialisation-list stage of its construction,
so the only step left in construction is for the constructor body to
execute. Given this is the body, what can go wrong? I haven't
pondered the Standard to see if this is undefined, but think there's
enough well defined behaviour in the knowledge that the implicit
construction steps should have completed before the constructor body,
to make the technique practical for use. Is there a compiler it
doesn't work on? Thanks, Tony

Frank Chang

unread,
Sep 9, 2005, 8:41:47 PM9/9/05
to
Werner, Ah, words of flattery, which I am not worthy of. I guess we
have to wait until the original poster informs us what the correct
solution is. Thank you

vadim alexandrov

unread,
Sep 10, 2005, 9:35:54 AM9/10/05
to
That should work too.

But, if say constructor opens a socket, then destructor should close
it.
The value of m may be irrelevant,

Why not redesign classs:

struct Y : public X {
Y( int x ) : X( x ) {}
Y( int x, int y ) : X( x+y ) {}
};

Code fix:

X::X(int a, int b ) : m( -1 ) {


this->~X(); // destroy itself
new (this) X( a+b ); // placement new
}

Vadim

?utf-8?Q?Ali_=C3=87ehreli?=

unread,
Sep 11, 2005, 11:17:18 AM9/11/05
to
I want to quote some of the earlier lines to give context to the discussion:

You had said:

<quote>


> Another way is:
>
> X::X(int a, int b) { *this = X(a + b); }

</quote>

Then I had said:

<quote>


The object on the left hand side of operator= is not constructed at the time
you call the assignment. Yes, some or all of its members are default
initialized, but the object is not.

By definition, the object on the left hand side will be constructed when the
constructor completes.

</quote>

"Tony Delroy" <tony_i...@yahoo.co.uk> wrote in message

news:1126272833.5...@g44g2000cwa.googlegroups.com...


> The object on the left hand side has passed the
> default-initialisation/initialisation-list stage of its construction,
> so the only step left in construction is for the constructor body to
> execute. Given this is the body, what can go wrong? I haven't
> pondered the Standard to see if this is undefined, but think there's
> enough well defined behaviour in the knowledge that the implicit
> construction steps should have completed before the constructor body,
> to make the technique practical for use. Is there a compiler it
> doesn't work on? Thanks, Tony

After thinking more about this, I agree with you that nothing can go wrong
with this particular case; or even in general, provided that the assignment
is the last line of the constructor body:

X::X(/* arguments */)
: /* the first part of the initialization */
{
/* the rest of the initialization */

// the last line of the constructor:
*this = X(/* different arguments */);

// WARNING: Don't add any lines here
}

There is nothing left for the constructor to do after the constructor body;
once we are in there, the final part of the construction is left to us.

I would be very uncomfortable to leave the safety of the object to warning
comments though. And no matter what, it feels wrong to construct an object
by

1) construct with one of the constructors

but, before leaving the body completely,

2) construct a temporary using another constructor

and finally

3) assign to this object from the temporary

The temporary in step 2 will require a destruction.

Since assignment is inherently a construction and a destruction, it adds to
the number of constructors and destructors (though those may be optimized
away). So, in addition to the uneasy feeling of assigning to a
not-completely-constructed object, it is also wasteful.

I agree though that this could be a suitable idiom for special cases like
the OP's question.

Ali

Frank Chang

unread,
Sep 11, 2005, 11:32:22 AM9/11/05
to
Vadim, Your idea is quite good. What if the value of m were relevant?
How would you handle that? My opinion is that both constructors X(int
a) and X(int a, int b) should be independently responsible for opening
sockets. What is your opinion? Thank you.

werasmus

unread,
Sep 12, 2005, 5:29:27 AM9/12/05
to
Frank Chang wrote:
> Werner, Ah, words of flattery, which I am not worthy of. I guess we
> have to wait until the original poster informs us what the correct
> solution is. Thank you

Hi Frank,

I Wonder whether the original poster will inform us of the correct
solution. Yes, he may yet be smiling at all our clutching at air at the
momemt. I have some new comments on your solution:

Your solution in (34):
X::X( int a, int b )
: m_( X( a+b ).m_{ }

What if the construction of X was very expensive? Every class could
use individual ways to solve this problem, depending on the physical
structure of the class.

A nice general solution may be to revert to using the pimpl idiom.

//X.h
class X
{
public:
X( int a );
X( int a, int b );

private:
struct Impl;
std::auto_ptr<Impl> const pimpl_;

};

//X.cpp
struct X::Impl
{
Impl( int m ) : m_( m ){ }

int const m_;
};

X::X( int a )
: pimpl_( new Impl( a ) )
{ ; }

X::X( int a, int b )
: pimpl_( new Impl( a+b ) )
{ ; }

Now matter how expensive construction of Impl, it is only required to
be constructed once. Its constructor also only requires one argument
as the interface can encapsulate how this argument should be passed
i.e. the interface may make assumptions about its implementation, as
the implementation is the encapsulation of the interface - for example:

: pimpl_( new Impl( a*MySpecialFactor ) ){} //and
: pimpl_( new Impl( (a+b)*MySpecialFactory ){}

The initialisation of Impl can also change without effecting the
interface.

Regards,

W

Branimir Maksimovic

unread,
Sep 12, 2005, 8:24:14 PM9/12/05
to

Krzysztof Żelechowski wrote:
>
>
>
> class X {
>
> int const m;
> public:
> X(int);
>
> X(int a, int b): X(a + b) {
>
> // do the same thing as X(a + b) does without knowing what it X(int) does
>
> }
>
> };

>
>
> I can think of solving this problem by splitting the class into two:
>
> class Xb {
> public:
> int const m;
> Xb(int);
> };
>
> class X: public Xb {
> X(int a): Xb(a) {
> }
> X(int a, int b): Xb(a + b) {
> }
> };

>
> However, this is very clumsy.

You can't call constructor in C++, constructors are called implicitly
as
side effect of objects creation.
If you don't like splitting class in two, you can construct recursive
structure of objects:

class X {
auto_ptr<X> p;
int const m;
public:
X(int i):p(0),m(i){}

X(int a, int b): p(new X(a + b)), m(0) {

// do the same thing as X(a + b) does without knowing what it X(int)

does
}
X(int a, int b, int c): p(new X(a*b,a*c)), m(0){
}
X(const X& in): p(in.p.get()?new X(*in.p):0), m(in.m) {
}
X& operator=(const X& in){
if(p.get() && in.p.get()) *p=*in.p;
// whatever
return *this;
}
// ~X(){}
void f(int i){ if (p.get()) p->f(i); else /* code */; }
};

This way you can remember how object is constructed :) and do some
exotic things if needed.

Greetings, Bane.

Frank Chang

unread,
Sep 12, 2005, 8:30:56 PM9/12/05
to
Werner, I compiled your code and tested it briefly. I have a question
about your implementation of the Pimpl pattern in the class X. I was
wondering why you used an auto_ptr to hold the pimpl pointer instead of
a raw pointer. The only reason I can think of it is that you wanted to
avoid the shallow copy/deep copy problem in X's copy constructor and
X's assignment operator. However, using the auto_ptr to hold a pimpl
pointer is problematic because of the potential for transfer of
ownership issues. Have you looked at the boost::shared_ptr class? It
might help you avoid some problems with auto_ptr's. Thank you

kanze

unread,
Sep 13, 2005, 5:09:06 AM9/13/05
to
Frank Chang wrote:

> Werner, I compiled your code and tested it briefly. I have a
> question about your implementation of the Pimpl pattern in the
> class X. I was wondering why you used an auto_ptr to hold the
> pimpl pointer instead of a raw pointer.

I missed earlier parts of this thread, but one thing is certain:
you cannot use an std::auto_ptr for the compilation firewall
idiom. It's undefined behavior to instantiation
std::auto_ptr<T> if T is an incomplete type. (See §17.4.3.6/2.)

> The only reason I can think of it is that you wanted to avoid
> the shallow copy/deep copy problem in X's copy constructor and
> X's assignment operator. However, using the auto_ptr to hold
> a pimpl pointer is problematic because of the potential for
> transfer of ownership issues. Have you looked at the
> boost::shared_ptr class? It might help you avoid some
> problems with auto_ptr's.

I don't think that boost::shared_ptr is appropriate either,
unless you're trying to do some sort of copy on write. The fact
that a class uses the compilation firewall idiom should be
transparent to the user -- using reference semantics (shallow
copy) rather than value semantics is anything but transparent.

IMHO, boost's scoped_ptr would be the only appropriate smart
pointer, although given the simplicity of the problem, I'm not
sure that a smart pointer is necessary, or even appropriate.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

werasmus

unread,
Sep 13, 2005, 5:05:45 AM9/13/05
to

Frank Chang wrote:
> Werner, I compiled your code and tested it briefly. I have a question
> about your implementation of the Pimpl pattern in the class X. I was
> wondering why you used an auto_ptr to hold the pimpl pointer instead of
> a raw pointer. The only reason I can think of it is that you wanted to
> avoid the shallow copy/deep copy problem in X's copy constructor and
> X's assignment operator.

Yes, the auto_ptr is const - therefore it cannot be used for transfer
of ownership. Also - I could probably explicitly have inherited from
boost::non_copyable - using the const auto_ptr prevents copying and
assignment (as it is not copyable/assignable). This means the class
itself becomes non copyable, as it has a non copyable member.
Admittedly, I have not looked at boost as much as I should. I'm kind of
waiting for a good book to arrive, and I want it to settle more. I'm
starting though.

> However, using the auto_ptr to hold a pimpl
> pointer is problematic because of the potential for transfer of
> ownership issues.

Transfer of ownership not possible because of constness of auto_ptr.
Also, you are right - using a raw pointer for the pimpl idiom is fine.

Regards,

Werner

werasmus

unread,
Sep 13, 2005, 8:36:43 AM9/13/05
to

kanze wrote:
> Frank Chang wrote:

> I missed earlier parts of this thread, but one thing is certain:
> you cannot use an std::auto_ptr for the compilation firewall
> idiom. It's undefined behavior to instantiation
> std::auto_ptr<T> if T is an incomplete type. (See §17.4.3.6/2.)

See my other posting - note that auto_ptr was const. I agree that I
needn't of used auto_ptr - raw one would do. As far as the incomplete
type is concerned, I don't think the type is necessarily incomplete.

class X
{
public:
~X();
private:
class Impl;
std::auto_ptr<Impl> pimpl_;
}

//Definition of Impl...
X::Impl{};

//Definition of X member functions...Is definition of X::Impl complete
// at this point? Yes, we are required to explicitly write the
// destructor
~X::X()
{
//No action required.
}

All said, I'll change my solution to:

class X : boost::non_copyable
{
//...Other
private:
class Impl;
Impl* pimpl_;
};

Frank Chang

unread,
Sep 13, 2005, 8:33:40 AM9/13/05
to
Werner, Thank you explaining the subtlety of the std::auto_ptr in the
pimpl idiom. James Kanze wrote a nice post today in reply to your
earlier post.
Regarding good books on boost, I have looked around in some
of the best technical bookstores in the world and I have found only one
book, Template Metaprogrammming. Do you know of any other books due to
be published soon? I guess the best way to learn about boost for now is
to participate in comp.lang.c++.moderated and learn from each other.
Thank you.

Frank Chang

unread,
Sep 13, 2005, 8:34:27 AM9/13/05
to
James, Thank you for explaining the use of std::auto_ptr in pimpl idiom
and the boost::scoped pointer. I read on a blog that you cannot use an
std::auto_ptr for the compilation firewall idiom, but it didn't give a
reason so I thought I that , well, transfer of ownership, is an issue
with std::auto_ptr. I will try the scoped pointer suggestion.

Tony Delroy

unread,
Sep 13, 2005, 8:34:48 AM9/13/05
to
Hi Ali,

I think it's ugly too, and I don't recall having used it, but do think
it's practical if you're lazy and more concerned about brevity of
source than performance. Might consider it if it avoided a really
repetitive constructor body while I was hacking together something for
a one-off, throw-away benchmark or proof-of-concept.

Cheers,
Tony

Ali Çehreli

unread,
Sep 14, 2005, 2:58:20 AM9/14/05
to
"werasmus" <w_e...@telkomsa.net> wrote in message
news:1126604669....@g49g2000cwa.googlegroups.com...

>
> kanze wrote:
>> Frank Chang wrote:
>
>> I missed earlier parts of this thread, but one thing is certain:
>> you cannot use an std::auto_ptr for the compilation firewall
>> idiom. It's undefined behavior to instantiation
>> std::auto_ptr<T> if T is an incomplete type. (See 17.4.3.6/2.)

I think you are referring to this text:

<quote>
if an incomplete type (3.9) is used as a template argument when
instantiating a template component.
</quote>

I think we need to take "template component" as the destructor of auto_ptr
in that context. It is when that function is "instantiated."

>
> See my other posting - note that auto_ptr was const. I agree that I
> needn't of used auto_ptr - raw one would do.

Raw one would do only in trivial cases like the example you've posted. As
soon as C's constructor becomes complicated enough to letexceptions escape,
the memory that pimpl_ points to will be leaked. As we all know, not handing
a new'ed resource immediately to a smart pointer is dangerous.

> As far as the incomplete
> type is concerned, I don't think the type is necessarily incomplete.
>
> class X
> {
> public:
> ~X();

That line is crucial.

> private:
> class Impl;
> std::auto_ptr<Impl> pimpl_;
> }
>
> //Definition of Impl...
> X::Impl{};
>
> //Definition of X member functions...Is definition of X::Impl complete
> // at this point? Yes, we are required to explicitly write the
> // destructor
> ~X::X()
> {
> //No action required.
> }

I agree with you. In fact, I had written an article on this very subject:

http://accu-usa.org/2002-02.html

I welcome any feedback...

Thank you,
Ali

--
ACCU's Silicon Valley Chapter meets on second Tuesdays. The meetings are
open to public and free of charge. Please come tonight for a talk on Ada:

http://accu-usa.org/index.html

E. Mark Ping

unread,
Sep 14, 2005, 3:00:20 AM9/14/05
to
In article <1126597715.5...@g49g2000cwa.googlegroups.com>,

kanze <ka...@gabi-soft.fr> wrote:
>I missed earlier parts of this thread, but one thing is certain:
>you cannot use an std::auto_ptr for the compilation firewall
>idiom. It's undefined behavior to instantiation
>std::auto_ptr<T> if T is an incomplete type. (See §17.4.3.6/2.)

...

>I don't think that boost::shared_ptr is appropriate either,
>unless you're trying to do some sort of copy on write. The fact
>that a class uses the compilation firewall idiom should be
>transparent to the user -- using reference semantics (shallow
>copy) rather than value semantics is anything but transparent.
>
>IMHO, boost's scoped_ptr would be the only appropriate smart
>pointer, although given the simplicity of the problem, I'm not
>sure that a smart pointer is necessary, or even appropriate.

I don't understand why auto_ptr<T> is undefined but scoped_ptr doesn't
present the same problem (granted, I've never used scoped_ptr though
I've started reading up on the boost smart ptrs).

After reading the paragraph in the standard you cited all I can find
is that the boost classes aren't in the standard. Is that the only
difference? I can't imagine the implementation being materially
different (where the functionality is similar).
--
Mark Ping
ema...@soda.CSUA.Berkeley.EDU

werasmus

unread,
Sep 14, 2005, 3:20:42 AM9/14/05
to

Ali Çehreli wrote:
> "Tony Delroy" <tony_i...@yahoo.co.uk> wrote in message
> news:1125568114.9...@o13g2000cwo.googlegroups.com...

> > Another way is:
> >
> > X::X(int a, int b) { *this = X(a + b); }
>
> Won't work.

Yes, it won't work, but not for the reason you mention. At the time you
enter the constructor, the this pointer is certainly valid, and can be
dereferenced. If at the time you dereference the this pointer, all
members have been created - as in this case, one can assign to it. One
would be able to assign to it when the constructor returns - there is
nothing more that is done wrt. allocation etc, therefore I would reason
the code here above is valid - whether good practice may be another
argument, but... it won't work because of <m> being const. This
constrains one from assigning to the object. Also <m> needs to be
initialised in the member initialiser list - something that the example
here above have omitted.

>
> The object on the left hand side of operator= is not constructed at the time
> you call the assignment. Yes, some or all of its members are default
> initialized, but the object is not.

I beg to differ - when all the members of the object is initialised,
and all that is required is the actual returning from the constructor,
then the object is as good as constructed - after all, returning from a
constructor cannot throw. If <m> was not const, and assignment was
legal at that point(IHMO), then the object construction could fail as
result of the assignment. In that case the object on the lhs's
construction will fail from an external perspective (the perpective of
the constructor callee). If I'm wrong here, someone would have to point
this to me by referring to standard. It would not seem consistent
though, as at the time of entering the constructor, all members are
valid (this includes members that require dynamic allocation if they
were initialised in the member initialiser list).

Regards,

Werner

kanze

unread,
Sep 14, 2005, 5:49:31 AM9/14/05
to
Ali Çehreli wrote:
> "werasmus" <w_e...@telkomsa.net> wrote in message
> news:1126604669....@g49g2000cwa.googlegroups.com...

> > kanze wrote:
> >> Frank Chang wrote:

> >> I missed earlier parts of this thread, but one thing is
> >> certain: you cannot use an std::auto_ptr for the
> >> compilation firewall idiom. It's undefined behavior to
> >> instantiation std::auto_ptr<T> if T is an incomplete type.
> >> (See 17.4.3.6/2.)

> I think you are referring to this text:

> <quote>
> if an incomplete type (3.9) is used as a template argument when
> instantiating a template component.
> </quote>

Exactly.

> I think we need to take "template component" as the destructor
> of auto_ptr in that context.

The "template component", here, is std::auto_ptr. The class
itself.

> It is when that function is "instantiated."

That's not what the standard says. The standard says that *any*
use of an incomplete type as an argument to a "template
component" is undefined behavior.

This is probably too restrictive; it even forbids things like:

#include <vector>
class X ;
void f( std::vector<X> const& ) ;

At the least, it should only be undefined behavior if the
argument is used in a context which requires instantiation.

But that wouldn't change anything here: if auto_ptr is a member
of X, the class auto_ptr requires instantiation. (See
§14.7.1/1, "[...], the class template specialization is
implicitly instantiated when the specialization is referenced in
a context that requires a completely-defined object type[...]".
Members of a class must be completely-defined object types, so
the class definition itself provokes instantiation of the
template every time, even if the class is never used.)

> > See my other posting - note that auto_ptr was const. I agree
> > that I needn't of used auto_ptr - raw one would do.

> Raw one would do only in trivial cases like the example you've
> posted. As soon as C's constructor becomes complicated enough
> to letexceptions escape, the memory that pimpl_ points to will
> be leaked.

We're talking here about a specific case, the compilation
firewall idiom. C's constructor is always simply:

C::C( parameters )
: pImpl( new Impl( parameters )
{
}

By definition. Other idioms have different requirements.

If I did want to do something like this, but with additional
processing in C::C, I would do something like:

C::C( parameters )
{
std::auto_ptr< Impl > p( new Impl ) ;
// whatever ...
pImpl = p.release() ;
}

or (more likely) use boost::unique_ptr. In any case, auto_ptr
cannot legally be used here (nor does it have the desired
semantics).

> As we all know, not handing a new'ed resource immediately to a
> smart pointer is dangerous.

Handing them to the wrong smart pointer is equally dangerous.
Smart pointers aren't a panacea.

FWIW: I hardly ever use reference counted pointers. Installing
the Boehm collector turned out to be so easy, I can no longer
imagine writing C++ without it. auto_ptr remains useful where
explicit transfer of ownership is desired, say in an interface
between threads, and boost::unique_ptr is still a good solution
for something like this, if Impl needs deterministic
destruction. But most of my pointers are raw pointers; why
should I worry, when the Boehm collector will do it for me, much
better.

> > As far as the incomplete type is concerned, I don't think
> > the type is necessarily incomplete.

> > class X
> > {
> > public:
> > ~X();

> That line is crucial.

> > private:
> > class Impl;
> > std::auto_ptr<Impl> pimpl_;

The type is incomplete here. auto_ptr is used in a context
where a complete type is required. According to the standard,
there is thus undefined behavior.

> > }

> > //Definition of Impl...
> > X::Impl{};

> > //Definition of X member functions...Is definition of X::Impl complete
> > // at this point? Yes, we are required to explicitly write the
> > // destructor
> > ~X::X()
> > {
> > //No action required.
> > }

> I agree with you. In fact, I had written an article on this
> very subject:

> http://accu-usa.org/2002-02.html

> I welcome any feedback...

It happens to be incorrect, that's all. For whatever reasons,
the standard says that any use of an incomplete type as an
argument to a template component is undefined behavior. Any
use. Period. IMHO, the requirement is far too restrictive; it
even forbids cases where the compiler is not required to
instantiate the template. But that's what the standard says.

If you're only targetting one implementation, and it works in
that implementation, of course, why not. But you must be aware
that it is undefined behavior, and that there are alternative
solutions which aren't.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

kanze

unread,
Sep 14, 2005, 5:46:49 AM9/14/05
to
E. Mark Ping wrote:
> In article <1126597715.5...@g49g2000cwa.googlegroups.com>,
> kanze <ka...@gabi-soft.fr> wrote:

> >I missed earlier parts of this thread, but one thing is
> >certain: you cannot use an std::auto_ptr for the compilation
> >firewall idiom. It's undefined behavior to instantiation
> >std::auto_ptr<T> if T is an incomplete type. (See
> >§17.4.3.6/2.)
> ...

> >I don't think that boost::shared_ptr is appropriate either,
> >unless you're trying to do some sort of copy on write. The
> >fact that a class uses the compilation firewall idiom should
> >be transparent to the user -- using reference semantics
> >(shallow copy) rather than value semantics is anything but
> >transparent.

> >IMHO, boost's scoped_ptr would be the only appropriate smart
> >pointer, although given the simplicity of the problem, I'm
> >not sure that a smart pointer is necessary, or even
> >appropriate.

> I don't understand why auto_ptr<T> is undefined but scoped_ptr
> doesn't present the same problem (granted, I've never used
> scoped_ptr though I've started reading up on the boost smart
> ptrs).

Because the standard says that any use of a template component
(like auto_ptr) with an argument which is an incomplete type is
undefined behavior.

Strictly speaking, I think I'm wrong about boost::scoped_ptr,
although in this case, I think that the problem is in the
wording of Boost's guarantees, and that the intent is that it
should be usable. Basically, Boost says: "T may be an
incomplete type at the point of smart pointer declaration.
Unless otherwise specified, it is required that T be a complete
type at points of smart pointer instantiation." I think that
this is actually misworded, since declaring a smart pointer in a
class definition is a point of instantiation, and later text
suggests that the Boost pointers should be usable in the
compilation firewall idiom, which is not the case if the above
guarantee is all that is given.

In fact, in the detailed documentation of scoped_ptr, it is
specified for each function whether the type must be complete or
not. I interpret this has having precedence over the reference
to the common pointer requirements quoted above, and as implying
(at least) that I can instantiate the class with an incomplete
type.

> After reading the paragraph in the standard you cited all I
> can find is that the boost classes aren't in the standard. Is
> that the only difference? I can't imagine the implementation
> being materially different (where the functionality is
> similar).

Adding the ability to allow instantiation of the class over an
incomplete type is an added restriction on the implementation.
I can't imagine that it is very useful in this case, but the
fact remains that the standard does not restrict implementations
in this way, and Boost does. It's a question of contractual
guarantees, and in the end, an interface specification is free
to give or withhold the guarantees it wishes. (Obviously, if it
doesn't give enough guarantees, no one will use it, and if it
makes too many restrictions on the implementation, no one will
implement it -- in an extreme case, no implementation may be
possible.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

kanze

unread,
Sep 14, 2005, 5:52:23 AM9/14/05
to
werasmus wrote:
> kanze wrote:
> > Frank Chang wrote:

> > I missed earlier parts of this thread, but one thing is
> > certain: you cannot use an std::auto_ptr for the compilation
> > firewall idiom. It's undefined behavior to instantiation
> > std::auto_ptr<T> if T is an incomplete type. (See
> > §17.4.3.6/2.)

> See my other posting - note that auto_ptr was const.

But that's irrelevant. The standard says that any use of
auto_ptr<T> where T is an incomplete type is undefined
behavior. (In practice, I think that there have been
implementations where it didn't work.)

> I agree that I needn't of used auto_ptr - raw one would do.
> As far as the incomplete type is concerned, I don't think the
> type is necessarily incomplete.

> class X
> {
> public:
> ~X();
> private:
> class Impl;
> std::auto_ptr<Impl> pimpl_;

At this point, Impl is an incomplete type.

> }

> //Definition of Impl...
> X::Impl{};

If the definition is in the header file, what's the point of
using Impl. If it's not, anyone who uses class X with just the
header will provoke the instantiation of auto_ptr on an
incomplete type.

Note that Boost has taken particular precautions and guarantees
(for shared_ptr and unique_ptr) that the type need only be
complete when its constructor and its destructor are
instantiated. If the constructors and the destructor of X are
not inline, this will only be in the implementation files of X,
where presumably X::Impl is complete.

> //Definition of X member functions...Is definition of X::Impl complete
> // at this point? Yes, we are required to explicitly write the
> // destructor

> ~X::X()
> {
> //No action required.
> }

Logically, you're on track, and this precaution is sufficient
with the Boost smart pointers. The standard allows an
implementation to take more liberties with auto_ptr, however,
and requires the type to be complete anytime the class itself is
instantiated. (Boost requires it to be complete anytime the
destructor, or any other function which might provoke a delete,
is instantiated.)

> All said, I'll change my solution to:

> class X : boost::non_copyable
> {
> //...Other
> private:
> class Impl;
> Impl* pimpl_;
> };

That's roughly what I do. Unless I want to support copying, in
which case I'll add the necessary user defined functions to
provide a deep copy. (Generally, copying implies a value
oriented class with mostly primitive types, so the compilation
firewall idiom isn't relevant. But there are some exceptions.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

kanze

unread,
Sep 14, 2005, 5:52:02 AM9/14/05
to
werasmus wrote:

[...]


> Admittedly, I have not looked at boost as much as I
> should. I'm kind of waiting for a good book to arrive, and I
> want it to settle more. I'm starting though.

Boost is big; you probably don't want to even know about all of
it. On the other hand, individual parts are often quite
abordable, even without a book -- I'd put the smart pointers in
this group (although shared_ptr can be tricky at times -- you
almost never want to use it to just call the destructor).

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Carl Barron

unread,
Sep 14, 2005, 11:38:31 AM9/14/05
to

>
> I missed earlier parts of this thread, but one thing is certain:
> you cannot use an std::auto_ptr for the compilation firewall
> idiom. It's undefined behavior to instantiation
> std::auto_ptr<T> if T is an incomplete type. (See §17.4.3.6/2.)

std::auto_ptr<T> is a class not a function. [17.4.3.6/2] applies
to functions.
I see nothing in 20.4.5 that makes std::auto_ptr<T> require a complete
type.

Carl Barron

unread,
Sep 14, 2005, 11:46:30 AM9/14/05
to
In article <1126687778....@f14g2000cwb.googlegroups.com>,
kanze <ka...@gabi-soft.fr> wrote:

> Because the standard says that any use of a template component
> (like auto_ptr) with an argument which is an incomplete type is
> undefined behavior.

Where?? if so then tr1::shared_ptr would violate the standard. Or has
this property been removed after 2005-01-17?? [2.2.3 para 2 of that
document]

werasmus

unread,
Sep 15, 2005, 5:12:19 AM9/15/05
to
kanze wrote:
> But that's irrelevant. The standard says that any use of
> auto_ptr<T> where T is an incomplete type is undefined
> behavior. (In practice, I think that there have been
> implementations where it didn't work.)

Reading 20.4.5 there is no such pre-condition. There is a pre-condition
on the destruction of auto_ptr, though:

20.4.5/3.13 - expression delete get() is well formed.

I must agree with Carl's posting. Writing a destructor in X.cpp (where
definition of X::Impl is well formed) is a requirement.

The type <auto_ptr<Impl> > is not instantiated in the definition of X.
It is instantiated where it is called - the member initialiser list of
the constructor in X.cpp. At this point, the definition of X::Impl is
known.

AFAIC the following is not undefined:

//X.h
class X
{
//..omitted for brevity

struct Impl; //Yes, we are going to define the type...
std::auto_ptr<Impl> pimpl_; //Not undefined, type not instantiated
//at this point
};

//X.cpp - Please note cpp :)
struct X::Impl
{
//...
};

X::X()
: pimpl_( new Impl ) //Note, the is the first time we instantiate Impl,
//at this point behaviour not undefined, because definition of Impl
//available...
{
}

//Yes, explicitly writing the destructor is essential,
// as omitting to do this will cause the default destructor
// to be called - and that lives in the compilation unit
// of the client - where, yes - Impl's definition is not available.
X::~X()
{
//Deliberate!
}

> If the definition is in the header file, what's the point of
> using Impl.

No, I did not explicitly state that Impl is defined in X.cpp - I think
I may have in a previous example. I meant for Impl to be defined in
X.cpp( and am sorry for having been so brief) therefore I absolutely
agree with your above statement.

> If it's not, anyone who uses class X with just the
> header will provoke the instantiation of auto_ptr on an
> incomplete type.

This is (IMHO) not true. auto_ptr<Impl> is instantiated in file X.cpp
if the constructor is defined there, not in the compilation unit of the
client. This would be true if we relied on X's default constructor,
which would lead to the default constructor of auto_ptr being called,
however in this case, certainly not.

Also, auto_ptr<Impl> will never be used in the compilation unit of the
client - it's private to X's compilation unit, not so? If client
compilation units were to use it, they would require the definition
(and friendship) else compilation error.

> Note that Boost has taken particular precautions and guarantees
> (for shared_ptr and unique_ptr) that the type need only be
> complete when its constructor and its destructor are
> instantiated.

I don't see how this differs from auto_ptr, actually. What precautions
were necessary? auto_ptr just deletes and dereferences T. Therefore
the definition of T is required for this purpose. If we omit writing
the destructor and rely on the default/implicit, yes - problems as the
default/implicit destructor will exist in the client's compilation
unit. If we supply the destructor in the compilation unit of X where
Impl is well formed, no problem.


> > ~X::X()
> > {
> > //No action required.
> > }
>
> Logically, you're on track, and this precaution is sufficient
> with the Boost smart pointers.

I believe I'm on track with auto_ptr's as well. Refer to Item 37 in
Exceptional C++ (which BTW does exactly this). Also refer to the end -
const auto_ptr idiom - the way I used it initially was relevant. You
could maybe refer me to More Exceptional C++, Item 31. There they
describe all the caveats of the auto_ptr as member. I've written the
destructor of X explicitly. Also, the fact that auto_ptr<Impl> was
const, prevents the "Grand theft" caveat, apart from the fact that we
could inherit from boost::non_copyable to prevent assignment/copying.
On the other hand, if we need to - we could write a copy constructor or
assignment operator in X.cpp - of course.

Lastly - yes, I've downloaded a copy of boost and will exploit it in
good time. I've looked at scoped_ptr amongst others. I realise it's
big.

Thank you for your time,

Regards,

Werner

E. Mark Ping

unread,
Sep 15, 2005, 5:13:24 AM9/15/05
to
>Because the standard says that any use of a template component
>(like auto_ptr) with an argument which is an incomplete type is
>undefined behavior.

Which strongly suggests that the compiler firewall cannot be
implemented using a template class. Doesn't it?

>Strictly speaking, I think I'm wrong about boost::scoped_ptr,
>although in this case, I think that the problem is in the
>wording of Boost's guarantees, and that the intent is that it
>should be usable.

How can Boost guarantee more than the standard WRT template
implementations?

Granted, I'm not the most knowledgeable about parsing the standard,
nor do I have the experience of implementers. But I can't imagine why
this wouldn't work for templates but would work with a pointer to a
forward declared class, provided any implementation which uses the
template (declared with an incomplete type as a parameter) must have
the type fully defined. For typical usage, that means the
scoped_ptr/auto_ptr, etc. is declared in the class definition, but the
destructor can't be inline.

It seems to me that if this doesn't work with templates, pImple can't
work portably.
--
Mark Ping
ema...@soda.CSUA.Berkeley.EDU

Gene Bushuyev

unread,
Sep 15, 2005, 5:14:12 AM9/15/05
to
"kanze" <ka...@gabi-soft.fr> wrote in message
news:1126687778....@f14g2000cwb.googlegroups.com...

> E. Mark Ping wrote:
>> In article <1126597715.5...@g49g2000cwa.googlegroups.com>,
>> kanze <ka...@gabi-soft.fr> wrote:
>
>> >I missed earlier parts of this thread, but one thing is
>> >certain: you cannot use an std::auto_ptr for the compilation
>> >firewall idiom. It's undefined behavior to instantiation
>> >std::auto_ptr<T> if T is an incomplete type. (See
>> >§17.4.3.6/2.)
...
>> I don't understand why auto_ptr<T> is undefined but scoped_ptr
>> doesn't present the same problem (granted, I've never used
>> scoped_ptr though I've started reading up on the boost smart
>> ptrs).
>
> Because the standard says that any use of a template component
> (like auto_ptr) with an argument which is an incomplete type is
> undefined behavior.

I didn't have time to read thoroughly the whole section to get the context,
but 17.4.3.6 doesn't seem to be relevant here. The standard, of course,
doesn't prohibit template type arguments to be incomplete types in general,
so it would be cruel and unusual restricting all standard library facilities
this way.
The problem using auto_ptr with an incomplete type (as in Pimpl idiom) is
compiler generated destructor, which is inlined and calls destructors for
all nonstatic members. auto_ptr destructor would need to generate code for
deleting the pointer it holds. And that according to 5.3.5/5 is undefined
behavior. Solution is pretty simple, create an empty destructor, so compiler
wouldn't need to generate one:

#include <memory>

// forward declaration of implementation class
class FooImpl;

class Foo
{
std::auto_ptr<FooImpl> impl;
public:
// prevent compiler from generating dtor
~Foo();
};

// this code can go to implementation file

class FooImpl {/*whatever*/};

// empty destructor now is ok, since the type is complete
Foo::~Foo() {}

int main()
{
Foo foo;
}


- Gene

Frank Chang

unread,
Sep 15, 2005, 5:10:57 AM9/15/05
to
Carl, I think when James Kanze is talking about imcomplete classes and
auto_ptr's is discussed in greater depth in the following link.

http://www.c-langhelp.com/Advantage-of-pimpl-other-than-compiler-firewall-741261

Thank you.

kanze

unread,
Sep 15, 2005, 6:16:36 AM9/15/05