Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to get recursion in constructor?

184 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
to
Carl Barron 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.)

> std::auto_ptr<T> is a class not a function. [17.4.3.6/2] applies
> to functions.

The name of the section does say "Other functions", but the text
in the paragraph is clear: "the effects are undefined in the
following cases: [...] -- if an incomplete type is used as a
template argument when instantiating a template COMPONENT." Not
just a function, but any template component.

Note that in general, a complete type may be necessary to
instantiate a template class. It's easy to avoid in the case of
auto_ptr, but it is necessary in the case of some
implementations of std::basic_string, for example. In this
case, the standard takes the simple way out -- rather than go
into details of when a complete type is necessary, and when not,
it simply says that it is always necessary.

(With regards to the section title, note that the third point in
paragraph 2 definitly applies to classes as well as to
components. I suspect that the title is a hang over from an
earlier, simpler epoch.)

> I see nothing in 20.4.5 that makes std::auto_ptr<T> require a
> complete type.

That's because the general rules apply. There's nothing in
§21.3 which says a complete type is necessary for
std::basic_string, either, but it is -- in practice, as well as
theory.

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

Stefan Näwe

unread,
Sep 15, 2005, 6:16:58 AM9/15/05
to
Frank Chang wrote:
> 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

You meant

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

I guess.

/S.
--
Stefan Naewe
naewe.s_AT_atlas_DOT_de

kanze

unread,
Sep 15, 2005, 6:16:14 AM9/15/05
to
Carl Barron wrote:
> 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??

§17.4.3.6

> if so then tr1::shared_ptr would violate the standard.

tr1::shared_ptr is not a template component of the standard,
yet. Even if it becomes one, individual components can give
stronger guarantees than those generally given. (Similarly, of
course, specific implementations of auto_ptr can define certain
cases of undefined behavior, and make it work.)

> Or has this property been removed after 2005-01-17?? [2.2.3
> para 2 of that document]

I doubt it. It's important that some classes have this liberty
-- the small string optimization, for example, depends on it, as
does a quality implementation of basic_filebuf (which works
without buffering if allocation of a buffer fails).

What I would expect (or hope) is that additional guarantees are
given for some specific classes. IMHO, it wouldn't be going to
far for std::auto_ptr to assure the same guarantees as
boost::scoped_ptr (which I hope is also part of tr1). But
that's not the case in the current standard.

--
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 15, 2005, 9:53:34 AM9/15/05
to
In article <1126774843....@g49g2000cwa.googlegroups.com>,
kanze <ka...@gabi-soft.fr> wrote:

> > if so then tr1::shared_ptr would violate the standard.
>
> tr1::shared_ptr is not a template component of the standard,
> yet. Even if it becomes one, individual components can give
> stronger guarantees than those generally given. (Similarly, of
> course, specific implementations of auto_ptr can define certain
> cases of undefined behavior, and make it work.)

Tr1 contains the following smart pointers.

template <class T> class shared_ptr;
template <class T> class weak_ptr;
template <class T> class enable_weak_from_this;
and the exception class bad_weak_ptr;

nothing about scoped_ptr, scoped_array,shared_array, which are in boost.

the tr1 classes are described similiarly to the corresponding classes
of boost.

kanze

unread,
Sep 16, 2005, 4:48:22 AM9/16/05
to
E. Mark Ping wrote:
> 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.

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

It doesn't suggest anything. It explicitly says that the


compiler firewall cannot be implemented using a template

component (class or function) from the standard library.
Unless, of course, specific language concerning that component
gives a special exception.

There's no problem with using the Boost smart pointers here.
First, of course, because they aren't part of the standard
library, so the standard doesn't say what they guarantee or
don't guarantee. And second, because they explicitly guarantee
that you can instantiate the class (but not necessarily the
member functions) over an incomplete type.

> >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?

Why on earth can't they? If whatever they guarantee is
implementable?

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

I don't think it would create any great problem for an
implementor to give auto_ptr the same guarantees that Boost
gives scoped_ptr. And of course, an implementation is free to
define behavior that is otherwise undefined. The only problem
is that the standard doesn't give the necessary guarantees, and
as far as I know, nor do any of the implementations of the
standard library.

(I'll admit that I haven't actually tried it. Perhaps something
with regards to the interactions with auto_ptr_ref would cause a
problem. But I don't see one off hand.)

> It seems to me that if this doesn't work with templates,
> pImple can't work portably.

I'm not sure I see the relationship. First, of course, because
I've never used templates in my implementations of the
compilation firewall, so I don't see how any template issues
could affect its portability:-). Second, however, because there
is no doubt that a simple scoped_ptr template can be implemented
such that the class can be instantiated over an incomplete type:
Boost, in fact, has done it. Using boost::scoped_ptr is most
definitly legal (supposing that the destructor is not inline, of
course).

To be honest, I'm not sure that the standard doesn't go too far
in this case. With regards to auto_ptr, it's not really an
issue; the semantics of auto_ptr aren't correct for the
compilation firewall anyway. But as currently written,
something like:
std::vector< MyClass >* p ;
is also undefined behavior if MyClass is an incomplete type.
And I don't quite see why -- in this context, the compiler
should not even instantiate std::vector< MyClass >, and I cannot
possibly see how an implementation could really fail if the
template is not even instantiated. At the very least, the
standard could allow using an incomplete type as argument in
contexts where the template is not instantiated.

--
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 16, 2005, 4:47:32 AM9/16/05
to
Carl Barron wrote:
> In article
> <1126774843....@g49g2000cwa.googlegroups.com>, kanze
> <ka...@gabi-soft.fr> wrote:

> > > if so then tr1::shared_ptr would violate the standard.

> > tr1::shared_ptr is not a template component of the standard,
> > yet. Even if it becomes one, individual components can give
> > stronger guarantees than those generally given. (Similarly, of
> > course, specific implementations of auto_ptr can define certain
> > cases of undefined behavior, and make it work.)

> Tr1 contains the following smart pointers.

> template <class T> class shared_ptr;
> template <class T> class weak_ptr;
> template <class T> class enable_weak_from_this;
> and the exception class bad_weak_ptr;

> nothing about scoped_ptr, scoped_array,shared_array, which are in boost.

That's interesting. Because there is really very little use for
shared_ptr and its helpers, where as scoped_ptr has very useful
semantics (to implement a compilation firewall, for example).

(Off hand, the only use I can think of for shared_ptr is to
handle locking in a singleton in a multithreaded environment.)

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

David Abrahams

unread,
Sep 16, 2005, 8:29:09 AM9/16/05
to
"kanze" <ka...@gabi-soft.fr> writes:

> Carl Barron wrote:
>> Tr1 contains the following smart pointers.
>
>> template <class T> class shared_ptr;
>> template <class T> class weak_ptr;
>> template <class T> class enable_weak_from_this;
>> and the exception class bad_weak_ptr;
>
>> nothing about scoped_ptr, scoped_array,shared_array, which are in boost.
>
> That's interesting. Because there is really very little use for
> shared_ptr and its helpers, where as scoped_ptr has very useful
> semantics (to implement a compilation firewall, for example).

Oh, that's just silly, James. shared_ptr was proposed for TR1 because
it was the most broadly useful of all of the Boost smart pointers.
Anything you can do with scoped_ptr you can also do with shared_ptr.

> (Off hand, the only use I can think of for shared_ptr is to handle
> locking in a singleton in a multithreaded environment.)

You have a pretty limited imagination then. Have you seen the list of
recipies at http://www.boost.org/libs/smart_ptr/sp_techniques.html for
example?

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

Roland Pibinger

unread,
Sep 19, 2005, 2:13:37 AM9/19/05
to
On 16 Sep 2005 08:29:09 -0400, David Abrahams
<da...@boost-consulting.com> wrote:

>"kanze" <ka...@gabi-soft.fr> writes:
>> Because there is really very little use for
>> shared_ptr and its helpers, where as scoped_ptr has very useful
>> semantics (to implement a compilation firewall, for example).
>
>Oh, that's just silly, James. shared_ptr was proposed for TR1 because
>it was the most broadly useful of all of the Boost smart pointers.
>Anything you can do with scoped_ptr you can also do with shared_ptr.

In a recent article Stroustrup writes:
"The main "Smart pointer" is a reference counted pointer, shared_ptr,
intended for code where shared ownership is needed. When the last
shared_ptr to an object is destroyed, the object pointed to is
deleted. Smart pointers are popular, but should be approached with
care. They are not the panacea that they are sometimes presented to
be. In particular, they are far more expensive to use than ordinary
pointers, destructors for objects "owned" by a set of shared_ptrs will
run at unpredictable times, and if a lot of objects are deleted at
once because the last shared_ptr to them is deleted you can incur
"garbage collection delays" exactly as if you were running a general
collector. The costs primarily relate to free store allocation of use
count objects and especially to locking during access to the use
counts in threaded systems. Do not simply replace all your ordinary
pointers with smart_ptrs if you are concerned with performance or
predictability. These concerns kept smart_ptrs "ancestor",
counted_ptr, out of the 1998 standard. If it is garbage collection you
want, you might be better off simply using one of the available
garbage collectors"
http://www.research.att.com/~bs/DnE2005.pdf

IMO, the main weakness of "smart pointers" is not (only) bad
performance but situated on the conceptual level. The smartest
implementation cannot remedy that.

>> (Off hand, the only use I can think of for shared_ptr is to handle
>> locking in a singleton in a multithreaded environment.)
>
>You have a pretty limited imagination then. Have you seen the list of
>recipies at http://www.boost.org/libs/smart_ptr/sp_techniques.html for
>example?

Indeed, you need a lot of imagination to see possible benefits of
shared_ptr. Especially the first example (class FILE ...) is very
"imaginative". I wonder if everyboy understands it at first sight.

Best regards,
Roland Pibinger

Michal 'Khorne' Rzechonek

unread,
Sep 19, 2005, 2:20:54 AM9/19/05
to
On 12 Sep 2005 20:24:14 -0400
"Branimir Maksimovic" <bm...@volomp.com> wrote:
> You can't call constructor in C++

That is not true. Both constructor and destructor can be called by hand anytime
you want. One use for it is "delete this" idiom.

--
Michal 'Khorne' Rzechonek -- mail&jabber khorne(at)leto.homedns.org
http://khorne.jogger.pl

kanze

unread,
Sep 19, 2005, 7:43:16 AM9/19/05
to
David Abrahams wrote:
> "kanze" <ka...@gabi-soft.fr> writes:

> > Carl Barron wrote:
> >> Tr1 contains the following smart pointers.

> >> template <class T> class shared_ptr;
> >> template <class T> class weak_ptr;
> >> template <class T> class enable_weak_from_this;
> >> and the exception class bad_weak_ptr;

> >> nothing about scoped_ptr, scoped_array,shared_array, which are in boost.

> > That's interesting. Because there is really very little use
> > for shared_ptr and its helpers, where as scoped_ptr has very
> > useful semantics (to implement a compilation firewall, for
> > example).

> Oh, that's just silly, James. shared_ptr was proposed for TR1
> because it was the most broadly useful of all of the Boost
> smart pointers. Anything you can do with scoped_ptr you can
> also do with shared_ptr.

You can, but why would you want to. The advantage of scoped_ptr
(and auto_ptr) is that they have different assignment and copy
semantics than raw pointers. You use them when you don't want
the copy and assignment semantics of raw pointers.

In particular, both give totally deterministic destruction. If
you need deterministic destruction, it is far better to use a
scoped_ptr or an auto_ptr, than to use a shared_ptr, and hope
that no one else has a copy of it. And if you don't need
deterministic destruction (the most frequent case), the Boehm
collector does a much better job of it than shared_ptr.

At least on the machines I use, the Boehm collection is also
more portable. (It works with Sun CC.) In general, I found it
simple and quick to install, and if you don't need the fancy
features, like finalization, child's play to use. The result is
that I simply don't write new C++ code without it. And of
course, that means that shared_ptr is of significantly less
use. (There are still significant uses involving providing your
own "destructor", which needed delete. But they certainly
aren't as frequent as the uses for scoped_ptr or auto_ptr.)

> > (Off hand, the only use I can think of for shared_ptr is to
> > handle locking in a singleton in a multithreaded
> > environment.)

> You have a pretty limited imagination then. Have you seen the
> list of recipies at
> http://www.boost.org/libs/smart_ptr/sp_techniques.html for
> example?

It's interesting. Some of the ideas there, however, would be
better implemented using scoped_ptr (e.g. Using shared_ptr to
execute code on block exit). In other cases, a raw pointer with
garbage collection is an easier, less constraining solution. In
fact, most of the really interesting uses of shared_ptr are
those with custom deleters.

Don't get me wrong. I think shared_ptr is in fact a very useful
class. But I find that if you have the choice, scoped_ptr is
usually to be preferred, almost always, in fact, unless you are
using the custom deleter.

What I'm reacting to, of course, is the often expressed idea
that you should automatically use shared_ptr everywhere. In
fact, most of the uses I've actually seen are better handled by
garbage collection. shared_ptr can be used, but it's really
only a palliative -- nothing beats the real thing. For the
rest, I see two real applications, one of which can be summed up
as the "Using shared_ptr as a smart counted handle", and the
other which involved compatibility with other interfaces, like
COM, which require reference counting. My application (the only
one I've seen in actual code -- but COM isn't used very much on
the Unix platforms I work on) is a simple example of the first.

And I guess I didn't word it as well as I should. When I said
"the only use I can think of", I didn't mean that it was
impossible to imagine others (although I'll admit that the idea
with COM never occured to me), but simply that I couldn't think
of them, in the sense that I couldn't think of any cases where
they occured in my applications. If I think of the "smart
counted handle" uses: memory is handled by garbage collection,
so I don't use it there, and open files (another example, using
FILE*) is handled in the standard filebuf class (I don't use
FILE). That leaves mutex locks. I can imagine that there are
other cases, but off hand, I can't think of any -- based on my
current applications.

So that while I think that even with garbage collection, I think
that shared_ptr is useful enough to warrent standardization, I
think that scoped_ptr (and auto_ptr, but that is already
standard) is even more useful.

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

Ingolf Steinbach

unread,
Sep 19, 2005, 7:41:01 AM9/19/05
to
Hi,

Ali Çehreli wrote:
> "werasmus" <w_e...@telkomsa.net> wrote in message

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

Why would I use a smart/auto pointer for pimpl in the first
place? If I have to remember to implement a non-inline d'tor
to avoid any undefined behaviour, I could also use a raw
pointer and remember to delete the object. After all, X's
c'tor would probably look like
pimpl_(new Impl(....))
without any fancy (e.g. exception throwing) stuff and also
without initialization of any other class members (X should
have only one member variable), so exception safety would
not be an issue here.

Kind regards
Ingolf
--

Ingolf Steinbach Jena-Optronik GmbH
ingolf.s...@jena-optronik.de ++49 3641 200-147
PGP: 0x7B3B5661 213C 828E 0C92 16B5 05D0 4D5B A324 EC04

kanze

unread,
Sep 19, 2005, 7:42:20 AM9/19/05
to
Roland Pibinger wrote:
> On 16 Sep 2005 08:29:09 -0400, David Abrahams
> <da...@boost-consulting.com> wrote:
> >"kanze" <ka...@gabi-soft.fr> writes:
> >> Because there is really very little use for shared_ptr and
> >> its helpers, where as scoped_ptr has very useful semantics
> >> (to implement a compilation firewall, for example).

> >Oh, that's just silly, James. shared_ptr was proposed for
> >TR1 because it was the most broadly useful of all of the
> >Boost smart pointers. Anything you can do with scoped_ptr
> >you can also do with shared_ptr.

> In a recent article Stroustrup writes:

It's a shame I didn't see the article a couple of years ago:-).
Would have saved me a lot of work.

> "The main "Smart pointer" is a reference counted pointer,
> shared_ptr, intended for code where shared ownership is
> needed. When the last shared_ptr to an object is destroyed,
> the object pointed to is deleted. Smart pointers are popular,
> but should be approached with care. They are not the panacea
> that they are sometimes presented to be.

Like, for example, learning this the hard way. I made the
mistake on my last application of trying to use smart pointers
(an equivalent to shared_ptr) everywhere. I ended up having to
rewrite some parts because of circular references, and then

> In particular, they are far more expensive to use than
> ordinary pointers,

much of the main processing because reference counting was too
expensive (as proved by the profiler). In this case, because I
was already there, I retained the shared pointers for the (one)
owning instance, and rewrote the rest to use raw pointers
obtained by get -- had I known what I learned afterwards, I
would have skipped the smart pointers entirely (for this
particular object, at any rate), and simply destructed
everything manually in the destructor of the object which owned
the owning object.

[...]


> If it is garbage collection you want, you might be better off
> simply using one of the available garbage collectors"

This is my conclusion as well. Based on experience with the
Boehm collection, in the environments which I am familiar with
(large Unix based systems). I was actually surprised how easy
it was to install and use. Not just at the programming level, I
expected that. But also in terms of installation, linking,
makefiles, the works.

[Note that the following is no longer part of Stroustrups
article. Roland's posting made this clear by means of
formatting, which has gotten lost in the quoting process.]

> IMO, the main weakness of "smart pointers" is not (only) bad
> performance but situated on the conceptual level. The
> smartest implementation cannot remedy that.

Agreed if you are in fact talking about using counted pointers
for garbage collection. The problem is that you need to
consciously use special types of pointers when you don't want
any special semantics. Disagreed for other uses, however -- I
find the different ownership semantics of scoped_ptr and
auto_ptr very useful in expressing important concepts: "no one
else can use the object pointed to, ever", or "once I've given
you this object, it's yours -- I will not access it again,
ever." (I've found the latter guarantee very important for
thread safety.)

Smart pointers, including shared_ptr, at least when you provide
a custom "deleter" (which doesn't delete:-)) have their uses,
even if they make pretty poor garbage collectors. It's just
like Stroustrup said, however -- they are not a panacea, and a
knee jerk reaction of systematically using a shared_ptr instead
of a raw pointer will only get you into trouble.

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

Branimir Maksimovic

unread,
Sep 19, 2005, 1:36:15 PM9/19/05
to
Michal 'Khorne' Rzechonek wrote:
> On 12 Sep 2005 20:24:14 -0400
> "Branimir Maksimovic" <bm...@volomp.com> wrote:
> > You can't call constructor in C++
>
> That is not true. Both constructor and destructor can be called by hand anytime
> you want. One use for it is "delete this" idiom.

Perhaps I was wrong but then you can show syntax for calling
constructor/destructor and perhaps taking pointer(s) to them.
Then it would be easy to show OP how it can be done.
By the way, delete this is not about calling destructor, rather
about destructing object. You can't call any other member function
or touch anything inside object after that.

Greetings, Bane.

Michal 'Khorne' Rzechonek

unread,
Sep 20, 2005, 5:36:59 AM9/20/05
to
On 19 Sep 2005 13:36:15 -0400
"Branimir Maksimovic" <bm...@volomp.com> wrote:
> Perhaps I was wrong but then you an show syntax for calling

> constructor/destructor and perhaps taking pointer(s) to them.

// create an object
Foo foo;

// (*) not just a simple call - it destroys object, as delete this does
foo.~Foo();

// placement new is all about calling ctor by hand
new (&foo) Foo();

> By the way, delete this is not about calling destructor, rather
> about destructing object. You can't call any other member function
> or touch anything inside object after that.

The same applies to (*). You are only allowed to call a constructor as shown
above.

--
Michal 'Khorne' Rzechonek -- mail&jabber khorne(at)leto.homedns.org
http://khorne.jogger.pl

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

Gerhard Menzl

unread,
Sep 20, 2005, 5:40:45 AM9/20/05
to
Michal 'Khorne' Rzechonek wrote:

>>You can't call constructor in C++
>
> That is not true. Both constructor and destructor can be called by
> hand anytime you want. One use for it is "delete this" idiom.

You cannot call a constructor explicitly, as constructors don't have
names. Given a class A, the expression

A ();

may look like a constructor call, but what it really does is to create a
temporary of type A. This also involves memory allocation and is not
equivalent to a simple function call.

The constructs that are conceptually closest to an explicit constructor
call are in-place construction:

new (p) A;

and base initialization:

struct B
{
B (int i);
};

struct D : B
{
D () : B (42) {}
};

Neither construct is suitable for the intention of the original poster.

You can call a destructor explicitly, usually to destroy an object that
was constructed in place. What all this has to do with the delete this
idiom is beyond me.

--
Gerhard Menzl

#dogma int main ()

Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".

kanze

unread,
Sep 20, 2005, 7:22:22 AM9/20/05
to
Ingolf Steinbach wrote:

> Why would I use a smart/auto pointer for pimpl in the first
> place? If I have to remember to implement a non-inline d'tor
> to avoid any undefined behaviour, I could also use a raw
> pointer and remember to delete the object. After all, X's
> c'tor would probably look like
> pimpl_(new Impl(....))
> without any fancy (e.g. exception throwing) stuff and also
> without initialization of any other class members (X should
> have only one member variable), so exception safety would not
> be an issue here.

I sort of agree. The one possible motivation for using a
boost::scoped_ptr is that you won't accidentally get a compiler
supplied copy constructor or assignment operator with the wrong
semantics. Other than that, however, I don't see where a smart
pointer buys anything.

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

Khorne

unread,
Sep 20, 2005, 7:21:07 AM9/20/05
to
> Neither construct is suitable for the intention of the original poster.

As far as I understand, OP wanted to do this:

struct Foo {
int c;

Foo(int _c):c(_c) {
};

Foo(int a, int b) {
new (this) Foo(a+b);
};
};

int main() {
Foo foo(1,2);

std::cout << foo.c << std::endl;
};

> You can call a destructor explicitly, usually to destroy an object that
> was constructed in place. What all this has to do with the delete this
> idiom is beyond me.

Well, this is just the same :-)

Regards
--
Michael 'Khorne' Rzechonek

Alf P. Steinbach

unread,
Sep 20, 2005, 9:57:20 AM9/20/05
to
* Ali Ēehreli -> werasmus:

>
> 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's both fishy and brilliant and seemingly it works. I think the best way to
understand how the placement of the destructor definition matters in practice
(that Impl must be fully defined at that point) is by considering how the
placement of the constructor definition similarly matters. Now the question
is, can anyone point to the relevant parts of the standard that allows this,
or, should perhaps something be fixed in or added to the standard?

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?

werasmus

unread,
Sep 20, 2005, 9:58:28 AM9/20/05
to
> Why would I use a smart/auto pointer for pimpl in the first
> place? If I have to remember to implement a non-inline d'tor
> to avoid any undefined behaviour, I could also use a raw
> pointer and remember to delete the object.

We've already agreed on this. The issue now became whether it was
undefined behaviour when one did specify the destructor in "X.cpp".

> After all, X's
> c'tor would probably look like
> pimpl_(new Impl(....))
> without any fancy (e.g. exception throwing) stuff and also
> without initialization of any other class members (X should
> have only one member variable), so exception safety would
> not be an issue here.

Yes, using auto_ptr in this case would be an overkill - agreed. A
modified solution has already been provided that inherits from
boost::non_copyable and uses a raw pointer. For this, refer to:

http://groups.google.co.za/group/comp.lang.c++.moderated/msg/0ea820510349ac6b?hl=en&

We've agreed on this too (long ago) :).

Regards,

Werner

peter koch larsen

unread,
Sep 20, 2005, 10:00:18 AM9/20/05
to

Khorne wrote:
> > Neither construct is suitable for the intention of the original poster.
>
> As far as I understand, OP wanted to do this:
>
> struct Foo {
> int c;
>
> Foo(int _c):c(_c) {
> };
>
> Foo(int a, int b) {
> new (this) Foo(a+b);

This is simply undefined behaviour. You need at least a this->~Foo()
call before the in-place construction. When considering potential
errors such as exceptions, performance problems (an extra constructor-
and destructor call) and semantic meaning (the extra work might cause
side-effects) I do not really see this as a solution to the OP's
problem.

/Peter

Michal 'Khorne' Rzechonek

unread,
Sep 20, 2005, 8:14:36 PM9/20/05
to
On 20 Sep 2005 10:00:18 -0400

"peter koch larsen" <peter.ko...@gmail.com> wrote:
> This is simply undefined behaviour. You need at least a this->~Foo()
> call before the in-place construction.

Please explain why (an opportinity to learn!)

> When considering potential
> errors such as exceptions,

As I understand, throwing an exception from the ctor means that there is no
object constructed. The same applies to my code - when Foo::Foo(int,int) fails,
exception flies to the client's code from Foo::Foo and I see no place in the
middle when something Bad can happen, including memory leak.

> semantic meaning (the extra work might cause
> side-effects)

I guess that in this case programmer is aware of doing something Not Really
Nice(tm), so he is able not to shoot himself.

--
Michal 'Khorne' Rzechonek -- mail&jabber khorne(at)leto.homedns.org
http://khorne.jogger.pl

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

Branimir Maksimovic

unread,
Sep 20, 2005, 8:10:23 PM9/20/05
to
Michal 'Khorne' Rzechonek wrote:
> On 19 Sep 2005 13:36:15 -0400
> "Branimir Maksimovic" <bm...@volomp.com> wrote:
> > Perhaps I was wrong but then you an show syntax for calling
> > constructor/destructor and perhaps taking pointer(s) to them.
>
> // create an object
> Foo foo;
>
> // (*) not just a simple call - it destroys object, as delete this does
> foo.~Foo();

Oh, yes I see, one can call destructor. I completelly forgot about
that.

>
> // placement new is all about calling ctor by hand
> new (&foo) Foo();

Here is not clear to me whether you can do this.
placement new takes void* which means raw memory.
perhaps new (dynamic_cast<void*>(&foo))Foo() will do the trick;
obviously Foo needs at least one virtual function for that.
I don't know any other way to get pointer to begining of memory block,
except through dynamic_cast, but one can do this , I think:
char *p = new char[sizeof(Foo)]; // standard guarantees alignment
Foo *f = new (p) Foo();
f->~Foo();
f = new (p) Foo(42);
f->~Foo();
delete [] p;

>
> > By the way, delete this is not about calling destructor, rather
> > about destructing object. You can't call any other member function
> > or touch anything inside object after that.
>
> The same applies to (*). You are only allowed to call a constructor as shown
> above.

No, after delete this memory is freed so one can't call anything
on that memory.

Greetings, Bane.

kanze

unread,
Sep 21, 2005, 5:12:44 AM9/21/05
to
peter koch larsen wrote:
> Khorne wrote:
> > > Neither construct is suitable for the intention of the
> > > original poster.

> > As far as I understand, OP wanted to do this:

> > struct Foo {
> > int c;

> > Foo(int _c):c(_c) {
> > };

> > Foo(int a, int b) {
> > new (this) Foo(a+b);

> This is simply undefined behaviour.

Only if the class has a non-trivial destructor. Si §3.8.

> You need at least a this->~Foo() call before the in-place
> construction.

Now that would be undefined behavior. You're not allowed to
destruct an object until the constructor has finished.

What you probably need to do is call the destructor on all
completely constructed sub-objects which have non-trivial
destructors. Although even here, the standard is somewhat
wishy-washy. §3.8/4:

A program may end the lifetime of any object by reusing the
storage which the object occupies or by explicitly calling
the destructor for an object of a class type with a
non-trivial destructor. For an object of a class type with
a non-trivial destructor, the program is not required to
call the destructor explicitly before the storage which the
object occupies is reused or released; however, if there is
no explicit call to the destructor or if a delete-expression
is not used to release the storage, the destructor shall not
be implicitly called and any program that depends on the
side effects produced by the destructor has undefined
behavior.

Generally speaking, I would assume that any non-empty user
defined destructor has side-effects (like freeing memory) on
which the program depends. Examples of non-trivial destructors
without such side effects are implicitly generated destructors
in the case of polymorphic objects, or destructors which the
user provided simply to make the destructor virtual, or
protected, or some such. (Note, however, that the presence of
any sub-object with a non-trivial destructor with side effects
means that even the compiler generated destructor will be
non-trivial and have side effects.)

In the exact case given, there is no problem -- the only
sub-object of the class is an int, which has a trivial
destructor. The memory can thus be reused without calling the
destructor. Where you would get in trouble is a class something
like:

class C
{
public:
C( int x ) ;
C( int x, int y ) ;
// ...
private:
int myX ;
std::vector<int>myV ;
} ;

C::C( int x, int y )
{
myV.~std::vector<int>() ;
// undefined behavior without this line.
new (this) C( x + y ) ;
}

IMHO, this makes the idiom too fragile for normal use.

--
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 21, 2005, 5:12:22 AM9/21/05
to
Alf P. Steinbach wrote:
> * Ali Çehreli -> werasmus:

> > 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's both fishy and brilliant and seemingly it works.

The seemingly is the key word. His solution still contains
undefined behavior.

> I think the best way to understand how the placement of the
> destructor definition matters in practice (that Impl must be
> fully defined at that point) is by considering how the
> placement of the constructor definition similarly matters. Now
> the question is, can anyone point to the relevant parts of the
> standard that allows this, or, should perhaps something be
> fixed in or added to the standard?

The standard explicitly forbids it (§17.4.3.6): "In particular,


the effects are undefined in the following cases: [...] -- if an
incomplete type is used as a template argument when

instantiating a template component."

This is probably overly restrictive. It is possible to
implement auto_ptr so that you can instantiate the class (but
not necessarily all of the member functions) without requiring a
complete type -- arguably, this is the case for the "natural"
implementation. So the standard could require somewhat stronger
guarantees for std::auto_ptr. It doesn't, however. (Note that
TR1 does impose more guarantees for std::tr1::shared_ptr.)

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

David Abrahams

unread,
Sep 21, 2005, 8:51:49 AM9/21/05
to
rpb...@yahoo.com (Roland Pibinger) writes:

First of all, I don't know what that post has to do with the argument
made by James, which is that shared_ptr isn't very useful.

Secondly, yes, I know Bjarne has been railing against naive
application of smart pointers in general for years. I don't know what
data he has that indicates smart pointers cause problems, but I'm sure
it's anecdotal, or it would have been in a published paper by now.
And of course it's possible to find anecdotal evidence for almost
anything. So, just to keep things balanced, I can report that we have
developers from environments where performance is crucial (the game
industry) reporting that they use shared_ptr and weak_ptr almost
exclusively, and they have never been the cause of a performance
bottleneck. http://lists.boost.org/Archives/boost/2005/04/83604.php
http://lists.boost.org/Archives/boost/2005/04/83624.php
http://lists.boost.org/Archives/boost/2005/04/83630.php

> IMO, the main weakness of "smart pointers" is not (only) bad
> performance but situated on the conceptual level. The smartest
> implementation cannot remedy that.

Yes, I know you've railed against the very idea of smart pointers for
years. I still don't understand what your problem is with them. It
seems like you just don't like to see "new" abstractions.

>>> (Off hand, the only use I can think of for shared_ptr is to handle
>>> locking in a singleton in a multithreaded environment.)
>>
>>You have a pretty limited imagination then. Have you seen the list of
>>recipies at http://www.boost.org/libs/smart_ptr/sp_techniques.html for
>>example?
>
> Indeed, you need a lot of imagination to see possible benefits of
> shared_ptr.

That's interesting, since it seems to be among the most widely-used of
all the Boost libraries. If you look through
http://www.boost.org/doc/html/who_s_using_boost_.html you'll see more
mentions of the smart pointer library than of anything else.

> Especially the first example (class FILE ...) is very "imaginative".
> I wonder if everyboy understands it at first sight.

It only took me a couple of seconds to get it, so that's one data
point.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

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

David Abrahams

unread,
Sep 21, 2005, 8:50:06 AM9/21/05
to
"kanze" <ka...@gabi-soft.fr> writes:

> David Abrahams wrote:
>> "kanze" <ka...@gabi-soft.fr> writes:
>
>> > Carl Barron wrote:
>> >> Tr1 contains the following smart pointers.
>
>> >> template <class T> class shared_ptr;
>> >> template <class T> class weak_ptr;
>> >> template <class T> class enable_weak_from_this;
>> >> and the exception class bad_weak_ptr;
>
>> >> nothing about scoped_ptr, scoped_array,shared_array, which are in boost.
>
>> > That's interesting. Because there is really very little use
>> > for shared_ptr and its helpers, where as scoped_ptr has very
>> > useful semantics (to implement a compilation firewall, for
>> > example).
>
>> Oh, that's just silly, James. shared_ptr was proposed for TR1
>> because it was the most broadly useful of all of the Boost
>> smart pointers. Anything you can do with scoped_ptr you can
>> also do with shared_ptr.
>
> You can,

Thank you.

> but why would you want to.

a. That's not the point. You said it wasn't very useful. If
you can do all the same things with shared_ptr as you can with
scoped_ptr, it's at least as useful as scoped_ptr.

b. Answer: so you have to remember/know one fewer component. I'm not
necessarily advocating that approach, but it's certainly a sensible
reason to use shared_ptr in lieu of scoped_ptr.

> The advantage of scoped_ptr (and auto_ptr) is that they have
> different assignment and copy semantics than raw pointers. You use
> them when you don't want the copy and assignment semantics of raw
> pointers.

You left out the advantage their destruction semantics, which is the
defining characteristic of most smart pointers. Without the
destruction semantics, they would indeed be almost useless.

> In particular, both give totally deterministic destruction.

Ah, thank you. That's not a particular of "assignment and copy
semantics."

> If you need deterministic destruction, it is far better to use a
> scoped_ptr or an auto_ptr, than to use a shared_ptr, and hope that
> no one else has a copy of it.

Sure.

> And if you don't need deterministic destruction (the most frequent
> case), the Boehm collector does a much better job of it than
> shared_ptr.

IIUC the Boehm collector doesn't do destruction at all; it only
collects memory and runs finalizers. Am I misinformed? In any case,
Boehm says that finalizers and destructors are very different
(http://www.hpl.hp.com/personal/Hans_Boehm/popl03/web/html/slide_17.html)

When you need shared ownership with eager (nondeterministic)
destruction shared_ptr is just the ticket.



>> You have a pretty limited imagination then. Have you seen the
>> list of recipies at
>> http://www.boost.org/libs/smart_ptr/sp_techniques.html for
>> example?
>
> It's interesting. Some of the ideas there, however, would be
> better implemented using scoped_ptr (e.g. Using shared_ptr to
> execute code on block exit).

Yes. Part of the reason the recipe is there is to show that if you
just want to have a single tool that you know well, shared_ptr
probably handles your use case. Note that scoped_ptr wasn't TR1-ized,
while shared_ptr was.

> In other cases, a raw pointer with
> garbage collection is an easier, less constraining solution. In
> fact, most of the really interesting uses of shared_ptr are
> those with custom deleters.

Yes, that's one of the coolest featuers of shared_ptr.

> Don't get me wrong. I think shared_ptr is in fact a very useful
> class. But I find that if you have the choice, scoped_ptr is
> usually to be preferred, almost always, in fact, unless you are
> using the custom deleter.

Agreed, unless you want to keep your toolbox small.

> What I'm reacting to, of course, is the often expressed idea
> that you should automatically use shared_ptr everywhere. In
> fact, most of the uses I've actually seen are better handled by
> garbage collection. shared_ptr can be used, but it's really
> only a palliative -- nothing beats the real thing.

Well, they're really different "real things."

> So that while I think that even with garbage collection, I think
> that shared_ptr is useful enough to warrent standardization, I
> think that scoped_ptr (and auto_ptr, but that is already
> standard) is even more useful.

Maybe you should write a proposal, then?

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

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

David Abrahams

unread,
Sep 21, 2005, 8:50:54 AM9/21/05
to
"kanze" <ka...@gabi-soft.fr> writes:

> It's a shame I didn't see the article a couple of years ago:-).
> Would have saved me a lot of work.
>
>> "The main "Smart pointer" is a reference counted pointer,
>> shared_ptr, intended for code where shared ownership is
>> needed. When the last shared_ptr to an object is destroyed,
>> the object pointed to is deleted. Smart pointers are popular,
>> but should be approached with care. They are not the panacea
>> that they are sometimes presented to be.
>
> Like, for example, learning this the hard way. I made the
> mistake on my last application of trying to use smart pointers
> (an equivalent to shared_ptr) everywhere. I ended up having to
> rewrite some parts because of circular references,

If it were truly an equivalent to shared_ptr, you would have had
weak_ptr at your disposal, too. You'd have been aware of the issue
and probably would have never ended up with these circular references
in the first place.

> Smart pointers, including shared_ptr, at least when you provide
> a custom "deleter" (which doesn't delete:-)) have their uses,
> even if they make pretty poor garbage collectors. It's just
> like Stroustrup said, however -- they are not a panacea, and a
> knee jerk reaction of systematically using a shared_ptr instead
> of a raw pointer will only get you into trouble.

I'm surprised you made that mistake, frankly. Isn't the danger of
creating reference cycles something everyone is taught about when they
first encounter reference counting? Well, I suppose not, or we
wouldn't be having this conversation.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

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

Gerhard Menzl

unread,
Sep 21, 2005, 12:17:38 PM9/21/05
to
James Kanze wrote:

> In the exact case given, there is no problem -- the only
> sub-object of the class is an int, which has a trivial
> destructor. The memory can thus be reused without calling the
> destructor. Where you would get in trouble is a class something
> like:
>
> class C
> {
> public:
> C( int x ) ;
> C( int x, int y ) ;
> // ...
> private:
> int myX ;
> std::vector<int>myV ;
> } ;
>
> C::C( int x, int y )
> {
> myV.~std::vector<int>() ;
> // undefined behavior without this line.
> new (this) C( x + y ) ;
> }
>
> IMHO, this makes the idiom too fragile for normal use.

It also doesn't mix well with inheritance. You would have to ensure that
placement new is only used in the most derived class, otherwise the same
base class constructor could be executed on the same object more than
once. You don't want maintenance programmers to have to go down that road.


--
Gerhard Menzl

#dogma int main ()

Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".

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

Gerhard Menzl

unread,
Sep 21, 2005, 12:16:56 PM9/21/05
to
Khorne wrote:

>>Neither construct is suitable for the intention of the original
>>poster.
>
> As far as I understand, OP wanted to do this:
>
> struct Foo {
> int c;
>
> Foo(int _c):c(_c) {
> };
>
> Foo(int a, int b) {
> new (this) Foo(a+b);
> };
> };

See James Kanze's post and my reply for why this is not a good idea.

>>You can call a destructor explicitly, usually to destroy an object

>>that was constructed in place. What all this has to do with the this
>>delete idiom is beyond me.


>
> Well, this is just the same :-)

I don't follow you. In-place destruction means to destroy an object
without freeing the memory allocated for it; delete this means to
destroy an object from within. This is definitely not the same. And what
has in-place *construction* got to do with delete this?


--
Gerhard Menzl

#dogma int main ()

Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".

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

peter koch larsen

unread,
Sep 21, 2005, 2:06:01 PM9/21/05
to

kanze wrote:
> peter koch larsen wrote:
> > Khorne wrote:
> > > > Neither construct is suitable for the intention of the
> > > > original poster.
>
> > > As far as I understand, OP wanted to do this:
>
> > > struct Foo {
> > > int c;
>
> > > Foo(int _c):c(_c) {
> > > };
>
> > > Foo(int a, int b) {
> > > new (this) Foo(a+b);
>
> > This is simply undefined behaviour.
>
> Only if the class has a non-trivial destructor. Si §3.8.

Right. I did assume that the class used for demonstration was just a
simplification. If not the solution would be trivial and not brought up
here.


>
> > You need at least a this->~Foo() call before the in-place
> > construction.
>
> Now that would be undefined behavior. You're not allowed to
> destruct an object until the constructor has finished.

Yup - you're right. I overlooked the fact that the object was not
constructed yet.

>
> What you probably need to do is call the destructor on all
> completely constructed sub-objects which have non-trivial
> destructors. Although even here, the standard is somewhat
> wishy-washy. §3.8/4:
>

[snip]


> IMHO, this makes the idiom too fragile for normal use.

On this and other points we agree.


>
> --
> James Kanze GABI Software

/PEter

Roland Pibinger

unread,
Sep 22, 2005, 4:50:43 AM9/22/05
to
On 21 Sep 2005 08:51:49 -0400, David Abrahams
<da...@boost-consulting.com> wrote:

>rpb...@yahoo.com (Roland Pibinger) writes:
>
>> IMO, the main weakness of "smart pointers" is not (only) bad
>> performance but situated on the conceptual level. The smartest
>> implementation cannot remedy that.
>
>Yes, I know you've railed against the very idea of smart pointers for
>years. I still don't understand what your problem is with them. It
>seems like you just don't like to see "new" abstractions.

1. 'Smart' pointers are not pointers. 'Real' pointers are a C/C++
language construct with clearly defined syntax and semantics. Among
other things real pointers define the relationship between the type of
the pointer and the type of the pointee. Smart pointers do not (they
just define some operator*).

2. Real pointers are very lightweight and versatile objects that have
two clear tasks, referencing (different objects) and dereferencing. It
makes no sense to burden them with 'heavy duties' like resource
management.

3. shared_ptr has some 'features' that can be questioned like transfer
of ownership (form somewhere to a shared ptr), 'custom deleters',
clumsy syntax (which sometimes is 'typedef-ed away')), resource
management not encapsulated, ...

Maybe 'smart' pointers are just inelegant, even though they may work
somehow.

>> Indeed, you need a lot of imagination to see possible benefits of
>> shared_ptr.
>
>That's interesting, since it seems to be among the most widely-used of
>all the Boost libraries. If you look through
>http://www.boost.org/doc/html/who_s_using_boost_.html you'll see more
>mentions of the smart pointer library than of anything else.

Maybe shared_ptr is just the most understandable library in Boost (at
least people think so).

>> Especially the first example (class FILE ...) is very "imaginative".
>> I wonder if everyboy understands it at first sight.
>
>It only took me a couple of seconds to get it, so that's one data
>point.

How did you know that there is a custom deleter (looking only at the
code)?
How did you know what the custom deleter does?
What is the advantage of shared_ptr to an encapsulated File class for
which you can change the implementation to low-level-IO or iostreams?
(e.g. class File { read(...); write(...) }; )
Why do you have access to the 'managed' resource (FILE), anyway?
The stdio functions only need an incomplete class (fread(FILE*, ...),
right? What's the point of the example WRT 'incomplete classes'?
How do you check the return value of the custom deleter?
Do you really need refernce-counting in that case? What are you
counting?

The more I look at it the less I understand it.

Best regards,
Roland Pibinger

kanze

unread,
Sep 22, 2005, 5:18:54 AM9/22/05
to
David Abrahams wrote:
> rpb...@yahoo.com (Roland Pibinger) writes:

[...]
> > http://www.research.att.com/~bs/DnE2005.pdf

> First of all, I don't know what that post has to do with the
> argument made by James, which is that shared_ptr isn't very
> useful.

Woah. My argument was that it wasn't *as* *useful* as
scoped_ptr. That's not saying that it doesn't have uses.

And my posting was in reaction to an article where, once again,
shared_ptr was recommended when it wasn't the correct solution.
There is a current trend to present shared_ptr as a universal
solution to all lifetime of object issues. In my experience, it
doesn't solve any object lifetime issues, per se. It does help
in managing resources. If for some reason, you can't use
garbage collection, it can even *help* in managing memory (but
it isn't a transparent solution, and it is far better to use
garbage collection if you can). And with user defined
"deleters", it can be used to manage a lot of other resources,
which garbage collection doesn't touch -- the main use I see for
it, in fact, is in managing locks on things like a singleton.

What's significant about shared_ptr is that as far as it's
pointer-like behavior is concerned, it has basically the same
semantics as a raw pointer. So unless you have some special
resource to be managed, like a lock (or you don't have garbage
collection), then there is really no point in using it instead
of a raw pointer. Where as both auto_ptr and scoped_ptr have
assignment/copy semantics which are different from those of raw
pointers. When you want to impose some restrictions on the use
of a pointer, they should be used. Even with garbage
collection; the important thing is that you cannot assign/copy a
shared_ptr, and that once you do so with an auto_ptr, you have
lost your access to the object. The first is an essential
characteristic in the compilation firewall idiom (which was the
original theme here), and the second is exceptionally useful in
a multithreaded environment, where auto_ptr is used to pass off
ownership of an object to a different thread.

> Secondly, yes, I know Bjarne has been railing against naive
> application of smart pointers in general for years. I don't
> know what data he has that indicates smart pointers cause
> problems, but I'm sure it's anecdotal, or it would have been
> in a published paper by now.

Why publish the obvious? *Naïve* use of anything causes
problems. The problem today is that many people *are*
advocating naïve use of boost::shared_ptr -- using it
systematically instead of a raw pointer.

> And of course it's possible to find anecdotal evidence for
> almost anything. So, just to keep things balanced, I can
> report that we have developers from environments where
> performance is crucial (the game industry) reporting that they
> use shared_ptr and weak_ptr almost exclusively, and they have
> never been the cause of a performance bottleneck.

Given that no one I know has complained about them for
performance reasons, this seems like a strawman. (I don't think
that the performance they give is as good as that of real
garbage collection, but since both have adequate performance for
all my needs, I'm not going to bother studying the issue more in
detail.)

The complaint that Bjarne seems to be making (in your words, not
his), and the complaint that I make, is against naïve use. They
are a very poor substitute for garbage collection, if that is
the purpose, and just dropping them in everywhere, in the place
of raw pointers, *without* *careful* *thought* about what you
are doing, just doesn't work.

> > IMO, the main weakness of "smart pointers" is not (only) bad
> > performance but situated on the conceptual level. The
> > smartest implementation cannot remedy that.

> Yes, I know you've railed against the very idea of smart
> pointers for years. I still don't understand what your
> problem is with them. It seems like you just don't like to
> see "new" abstractions.

> >>> (Off hand, the only use I can think of for shared_ptr is
> >>> to handle locking in a singleton in a multithreaded
> >>> environment.)

> >>You have a pretty limited imagination then. Have you seen
> >>the list of recipies at
> >>http://www.boost.org/libs/smart_ptr/sp_techniques.html for
> >>example?

> > Indeed, you need a lot of imagination to see possible
> > benefits of shared_ptr.

> That's interesting, since it seems to be among the most
> widely-used of all the Boost libraries.

Could that be because most people aren't yet using garbage
collection? And that they do *help* manage memory, if you don't
have a fully automated solution.

> If you look through
> http://www.boost.org/doc/html/who_s_using_boost_.html you'll
> see more mentions of the smart pointer library than of
> anything else.

Note that in the case which triggered this discussion, I clearly
said that a scoped_ptr might be appropriate. IMHO, in this
particular case, the advantages aren't enough to warrent
introducing Boost if the application isn't already using it. On
the other hand, if the application is, scoped_ptr seems like a
good idea.

> > Especially the first example (class FILE ...) is very
> > "imaginative". I wonder if everyboy understands it at first
> > sight.

> It only took me a couple of seconds to get it, so that's one
> data point.

Well, you're probably a bit more skilled than the average
programmer in these things:-). But again, I suspect that it is
linked to the common *presentation* of shared_ptr -- use
shared_ptr everywhere, and your memory management problems will
simply disappear. Not only is this false, but it also hides the
fact that shared_ptr can manage any resource, not just memory.
Once this point has been pointed out, the first example becomes
obvious. (Of course, once you decide to use iostream, insteamd
of FILE*, the example itself becomes irrelevant:-). But most of
the applications I've seen use third party libraries which work
much like FILE*, and it really doesn't take much immagination to
transfer the idea.)

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

Ingolf Steinbach

unread,
Sep 22, 2005, 5:18:28 AM9/22/05
to
David Abrahams wrote:
> Anything you can do with scoped_ptr you can also do with shared_ptr.

Is that really so?

<nitpicking>
You can use scoped_ptr in an exception-free environment
while shared_ptr might throw on construction.
</nitpicking>

Kind regards
Ingolf
--

Ingolf Steinbach Jena-Optronik GmbH
ingolf.s...@jena-optronik.de ++49 3641 200-147
PGP: 0x7B3B5661 213C 828E 0C92 16B5 05D0 4D5B A324 EC04

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

kanze

unread,
Sep 22, 2005, 5:21:09 AM9/22/05
to
David Abrahams wrote:
> "kanze" <ka...@gabi-soft.fr> writes:

> > It's a shame I didn't see the article a couple of years
> > ago:-). Would have saved me a lot of work.

> >> "The main "Smart pointer" is a reference counted pointer,
> >> shared_ptr, intended for code where shared ownership is
> >> needed. When the last shared_ptr to an object is destroyed,
> >> the object pointed to is deleted. Smart pointers are
> >> popular, but should be approached with care. They are not
> >> the panacea that they are sometimes presented to be.

> > Like, for example, learning this the hard way. I made the
> > mistake on my last application of trying to use smart
> > pointers (an equivalent to shared_ptr) everywhere. I ended
> > up having to rewrite some parts because of circular
> > references,

> If it were truly an equivalent to shared_ptr, you would have
> had weak_ptr at your disposal, too. You'd have been aware of
> the issue and probably would have never ended up with these
> circular references in the first place.

Sure. I am aware of the issues. That doesn't mean that cycles
don't sneak in. As in every project I've ever worked on, I'm
not master of all of the code.

> > Smart pointers, including shared_ptr, at least when you
> > provide a custom "deleter" (which doesn't delete:-)) have
> > their uses, even if they make pretty poor garbage
> > collectors. It's just like Stroustrup said, however -- they
> > are not a panacea, and a knee jerk reaction of
> > systematically using a shared_ptr instead of a raw pointer
> > will only get you into trouble.

> I'm surprised you made that mistake, frankly. Isn't the
> danger of creating reference cycles something everyone is
> taught about when they first encounter reference counting?
> Well, I suppose not, or we wouldn't be having this
> conversation.

Being aware of a potential problem doesn't mean that you never
make a mistake. More to the point: in the end, the effort
necessary to analyse where there were cycles (and to manage the
order of destruction required by some third party libraries) was
more that the effort which would have been necessary to manually
manage the pointers. There are cases where shared_ptr can
replace garbage collection, if for some reason you cannot use
garbage collection, but there are others where it isn't
appropriate. In the end, none of the substitutes for garbage
collection are as good as real garbage collection.

--
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 22, 2005, 7:20:06 AM9/22/05
to

> > You can,

> Thank you.

Yes and no. Most of the time, it makes no difference whether I
use shared_ptr or a raw pointer. shared_ptr may mean that the
memory is freed earlier, but who cares -- garbage collection
will ensure that it is there when I need it, and probably more
efficiently.

The important thing about scoped_ptr (and auto_ptr) isn't what I
can do with them; it's what I can't do. If I don't want a
pointer to be copied, shared_ptr ensures that it won't be. And
if I don't want to access the object after assigning the pointer
to another pointer, auto_ptr will ensure that I don't.

If I look at my last application, the only places I used my
equivalent to shared_ptr was where garbage collection would have
done the job better. Where as I used auto_ptr intensively in
the interfaces between threads, and there were a couple of cases
where scoped_ptr might have been appropriate.

> b. Answer: so you have to remember/know one fewer component.
> I'm not necessarily advocating that approach, but it's
> certainly a sensible reason to use shared_ptr in lieu of
> scoped_ptr.

The basic problem with all smart pointers is that you need to
remember several components -- you can't totally avoid raw
pointers, and even with shared_ptr, you need special techniques
to handle cycles, or to get a shared_ptr from this. If
restricting the number of components you need to remember or
know is an issue, then just stick with raw pointers.

> > The advantage of scoped_ptr (and auto_ptr) is that they have
> > different assignment and copy semantics than raw pointers.
> > You use them when you don't want the copy and assignment
> > semantics of raw pointers.

> You left out the advantage their destruction semantics, which
> is the defining characteristic of most smart pointers.
> Without the destruction semantics, they would indeed be almost
> useless.

I've found very little use for the destruction semantics. When
a class needs destruction, 9 times out of 10, it will be
instantiated as a local variable, and the 10th time, it will
need explicit destruction, in response to a specific event.

auto_ptr and scoped_ptr are useful because they change the rules
of pointer assignment.

shared_ptr is useful because you can change the destruction
semantics, so that it doesn't call delete on the pointed to
object.

> > In particular, both give totally deterministic destruction.

> Ah, thank you. That's not a particular of "assignment and
> copy semantics."

Right. Which means that in some cases, scoped_ptr may be used
instead of an auto variable, e.g. if the object must be
polymorphic.

The important point here, of course, is that the destruction
*is* deterministic. So it can be used, if appropriate. On the
other hand, it's hardly the principal reason I use these
pointers; in the case of auto_ptr, I cannot think of a case off
hand where the deterministic destruction played a role in my
choice.

> > If you need deterministic destruction, it is far better to
> > use a scoped_ptr or an auto_ptr, than to use a shared_ptr,
> > and hope that no one else has a copy of it.

> Sure.

> > And if you don't need deterministic destruction (the most
> > frequent case), the Boehm collector does a much better job
> > of it than shared_ptr.

> IIUC the Boehm collector doesn't do destruction at all; it
> only collects memory and runs finalizers.

The way I use it, it doesn't even run finalizers.

> Am I misinformed? In any case, Boehm says that finalizers and
> destructors are very different
> (http://www.hpl.hp.com/personal/Hans_Boehm/popl03/web/html/slide_17.html)

Quite. In practice, you only rarely need destructors (and then
almost always only on auto variables), and to date, I've never
used a finalizer for anything but error detection.

> When you need shared ownership with eager (nondeterministic)
> destruction shared_ptr is just the ticket.

Well, I can accept that. And just when is that? I've never
encountered the case.

> >> You have a pretty limited imagination then. Have you seen
> >> the list of recipies at
> >> http://www.boost.org/libs/smart_ptr/sp_techniques.html for
> >> example?

> > It's interesting. Some of the ideas there, however, would
> > be better implemented using scoped_ptr (e.g. Using
> > shared_ptr to execute code on block exit).

> Yes. Part of the reason the recipe is there is to show that
> if you just want to have a single tool that you know well,
> shared_ptr probably handles your use case. Note that
> scoped_ptr wasn't TR1-ized, while shared_ptr was.

Which IMHO was an error. Of the three existing smart pointers
(standard or Boost), auto_ptr is by far the most useful, at
least in a threaded environment, and scoped_ptr is more useful
than shared_ptr.

Unless, of course, you ban garbage collection.

> > In other cases, a raw pointer with garbage collection is an
> > easier, less constraining solution. In fact, most of the
> > really interesting uses of shared_ptr are those with custom
> > deleters.

> Yes, that's one of the coolest featuers of shared_ptr.

That is, IMHO, a brilliant feature. It changes what was a poor
replacement for garbage collection into a generally useful
class, even when garbage collection is present.

> > Don't get me wrong. I think shared_ptr is in fact a very
> > useful class. But I find that if you have the choice,
> > scoped_ptr is usually to be preferred, almost always, in
> > fact, unless you are using the custom deleter.

> Agreed, unless you want to keep your toolbox small.

(I'm tempted to ays that if your goal is keeping your toolbox
small, you should avoid Boost. Because once you start looking
at Boost, there'll be so many things you'll want to add.)

With regards to pointers, keeping your toolbox small means just
using raw pointers. Or maybe adding auto_ptr, if you are
working in a multithreaded environment.

> > What I'm reacting to, of course, is the often expressed idea
> > that you should automatically use shared_ptr everywhere. In
> > fact, most of the uses I've actually seen are better handled
> > by garbage collection. shared_ptr can be used, but it's
> > really only a palliative -- nothing beats the real thing.

> Well, they're really different "real things."

In a way, yes. But the problem is that shared_ptr is being used
a lot as a palliative to the lack of garbage collection.

> > So that while I think that even with garbage collection, I
> > think that shared_ptr is useful enough to warrent
> > standardization, I think that scoped_ptr (and auto_ptr, but
> > that is already standard) is even more useful.

> Maybe you should write a proposal, then?

For a number of personal reasons, I'm not very active in the
standardization effort at present. If I had the time, I'd
probably consacrate it to getting garbage collection adopted;
IMHO, that's more important than scoped_ptr.

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

Peter Dimov

unread,
Sep 22, 2005, 3:50:09 PM9/22/05
to
kanze wrote:
> David Abrahams wrote:

> > When you need shared ownership with eager (nondeterministic)
> > destruction shared_ptr is just the ticket.
>
> Well, I can accept that. And just when is that? I've never
> encountered the case.

When you use weak_ptr to track object destruction/reference
invalidation. In a GC or raw pointer/manual delete scenario, you'd
achieve the same effect with a notification (manual/from destructor). I
prefer the less intrusive weak_ptr in most cases.

> Which IMHO was an error. Of the three existing smart pointers
> (standard or Boost), auto_ptr is by far the most useful, at
> least in a threaded environment, and scoped_ptr is more useful
> than shared_ptr.
>
> Unless, of course, you ban garbage collection.

In an alternate reality where the whole C++ community uses garbage
collection, shared_ptr would indeed have been less useful.

Bob Bell

unread,
Sep 23, 2005, 10:34:26 AM9/23/05
to
Roland Pibinger wrote:
> On 16 Sep 2005 08:29:09 -0400, David Abrahams

> <da...@boost-consulting.com> wrote:
> > You have a pretty limited imagination then. Have you seen the list of
> > recipies at http://www.boost.org/libs/smart_ptr/sp_techniques.html for
> > example?
>
> Indeed, you need a lot of imagination to see possible benefits of
> shared_ptr. Especially the first example (class FILE ...) is very

> "imaginative". I wonder if everyboy understands it at first sight.

I did; there's another data point.

I'll even take a crack at answering the questions you asked when Dave
said he got the example:

> How did you know that there is a custom deleter (looking only at the code)?

I didn't, when "looking only at the code"; I knew it when I read the
text of the example. Did I cheat? It was only one more sentence.

> How did you know what the custom deleter does?

Because there's only one thing it can do: call fclose().

> What is the advantage of shared_ptr to an encapsulated File class for
> which you can change the implementation to low-level-IO or iostreams?
> (e.g. class File { read(...); write(...) }; )

Your encapsulated File class must be completely defined; since the
example was about incomplete, opaque handles, this question is
irrelevant to the example.

> Why do you have access to the 'managed' resource (FILE), anyway?

The example is about opaque types; the fact that it's a FILE is less
important than the technique itself. The problem with exposing the
managed FILE is that someone could accidentally call fclose(sp.get()),
but the technique works just as well with an opaque type for which the
user has no access to the equivalent to fclose().

> The stdio functions only need an incomplete class (fread(FILE*, ...),
> right? What's the point of the example WRT 'incomplete classes'?

That shared pointer doesn't need a complete type as the pointee (as
opposed to, say, auto_ptr).

> How do you check the return value of the custom deleter?

What return value? I don't even understand this question.

> Do you really need refernce-counting in that case?

It's just an example of a technique; it's not advocating reference
counting for FILEs.

> What are you
> counting?

The number of references to the opaque type.

> The more I look at it the less I understand it.

So far we have three data points:

Roland: the more he looks at it, the less he understands it.
Dave: got it right away.
Bob: got it right away.

It's only three data points, but an interesting correlation seems to be
emerging:

Roland: biased against shared_ptr.
Dave: not biased.
Bob: not biased.

Hmm...

Bob

kanze

unread,
Sep 23, 2005, 10:09:00 PM9/23/05
to
Peter Dimov wrote:
> kanze wrote:
>> David Abrahams wrote:

>>> When you need shared ownership with eager
>>> (nondeterministic) destruction shared_ptr is just the
>>> ticket.

>> Well, I can accept that. And just when is that? I've never
>> encountered the case.

> When you use weak_ptr to track object destruction/reference
> invalidation. In a GC or raw pointer/manual delete scenario,
> you'd achieve the same effect with a notification (manual/from
> destructor). I prefer the less intrusive weak_ptr in most
> cases.

I suppose that could be useful. I've got a smart pointer which
does just that (and since my library code is almost entirely
based on code originally used in real applications, if it's in
my library, I did need it, at least once). But most of the
times I've needed notification, I've needed to to more than just
set a pointer to null.

>> Which IMHO was an error. Of the three existing smart
>> pointers (standard or Boost), auto_ptr is by far the most
>> useful, at least in a threaded environment, and scoped_ptr
>> is more useful than shared_ptr.

>> Unless, of course, you ban garbage collection.

> In an alternate reality where the whole C++ community uses
> garbage collection, shared_ptr would indeed have been less
> useful.

Well, given that garbage collection is generally available, and
easily installed, that sounds like a problem that should be
solved with education, and not added classes:-). (FWIW: it took
me less than an hour to download, install and activate the Boehm
collector in my makefiles. And I'm notoriously incompetent when
it comes to installing third party software -- despite many
attempts, I was never able to successfully install STLPort, for
example.)

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

0 new messages