Topic: Questions about auto_ptr implementation


Author: =?ISO-8859-1?Q?J=F6rg?= Barfurth <joerg.barfurth@attglobal.net>
Date: 2000/05/17
Raw View
[Note: I am crossposting this to csc++. But f-up-to is set to clc++m
only, as there was a related thraed on csc++ recently.]

Short answer first:

There is a recent thread on comp.std.c++ (or was it on clc++m ?)
dicussing this. Search deja to locate that.

Also see http://www.awl.com/cseng/titles/0-201-63371-X/auto_ptr.html
where Scott Meyers offers some explanations about auto_ptr and kindly
makes available the original proposal that made it into the standard.
That proposal also explains how that works.

Note that this exploits some obscure details of the standard, so maybe
you don't want to know this.
A regular poster on csc++ (and comittee member) once stated in that group
that (a) you don't really want to know this, and later he stated that (b)
there are only three people in the world that really understand it - two
of them being the authors of said proposal.

Also note that this proposal (and the standard) is defective. By
searching comp.std.c++ or the associated FAQ you should find a link to
the library issues list of the C++ Standardization Comittee. There is a
defect report about the auto_ptr conversions and a proposed resolution
for that defect. (Some people think that this resolution still is
defective:( ). You might be tempted to conclude that noone really
understands it completely ;-)

My attempt at explaining is interspersed below.

>>>>>>>>>>>>>>>>>> Urspr   ngliche Nachricht <<<<<<<<<<<<<<<<<<

Am 16.05.00, 17:48:20, schrieb "Pelle (Per Otterstroem)"
<pt96pot@student.hk-r.se> zum Thema Questions about auto_ptr
implementation:


> Hi everybody!

> I've tried to figure out how the auto_ptr implementation works. I
> understand the basics but the implicite type conversion thing is magic
> to me.

It is supposed to be ;-)

> Example:

> class Base {
>  public:
>   Base(int x) {mX = x}
>   int getX() {return mX}
>  private:
>   int mX;
> }

has no virtual destructor :-(

> class Derived {
>  public:
>   Derived(int x, int y) : Base(x) {mY = y}
>   int getY() {return mY}
>  private:
>   int mY;
> }

> void convert(const auto_ptr<Base>& b) {
>   int x;
>   x = b->getX()
> }

I'll explain what is going on there below.

> int main() {
>   auto_ptr<Derived> d(10, 20);
>   convert(d);
>   int y = d->getY(); // ERROR !?
> }

This doesn't compile. I suppose the first line should be:
    auto_ptr<Derived> d( new Derived(10,20) );

> Question 1: Why do I get a runtime error when calling d->getY()? The
> convert() function takes a reference, right?

Yes, but it must be a reference to const. A auto_ptr<Derived> is not a
auto_ptr<Base>, so it can't be bound to the reference directly.

It wouldn't compile with a reference to non-const, although d isn't
const. Nor would it compile if d were const, because then the conversions
wouldn't work.

Therefore a temporary auto_ptr<Base> is created. Copying an auto_ptr to
another one (even when combined with a conversion) always transfers
ownership and resets the original auto_ptr to NULL. [That is why the source
of the conversion must be non-const]

When the temporary is destructed (at the end of the full expression,
which here is after convert returns) the temporary also destroys the
object pointed to.

In this case, that means that the Derived object is deleted through a
pointer to a Base. As a virtual d'tor is missing, you have the first
instance of undefined behavior here.

Continuing in main(), d holds the NULL pointer by now, so dereferencing
it to call getY() is undefined again (and will crash most machines).

- BEGIN discussion of sample program output ---------------------------

> Question 2: How does the type conversion works? To figure it out I made
> some output in the methods in my auto_ptr template. (The interesting
> parts of the auto_ptr implementation is found at the bottom). The output
> from the above program using my homemade auto_ptr looks like this:

A) The original auto_ptr<Derived> is constructed:
> Constructor

B) Now copy-initialize a (temporary) auto_ptr<Base> from the original

B.1): Convert it to an auto_ptr<Base> using user-defined conversion:
> operator auto_ptr<U>()

B.1.1): This conversion operator resets the original auto_ptr to NULL

B.1.2): Construct the auto_ptr<Base>. Called by the conversion operator:
> Constructor

B.2 [*] Initialize a auto_ptr<Base> from that (rvalue) auto_ptr<Base>.

- By the wording of the standard it makes no difference, whether
  it is direct or copy-initialization, if both types are the same
  (formally it is copy-initialization here).

B.2.1): The copy c'tor argument can't bind to an rvalue. So the only
viable constructor is from an auto_ptr_ref<Base>. Convert the argument
accordingly, using the conversion operator:
> operator auto_ptr_ref<U>()

B.2.1.1): This conversion operator resets the first auto_ptr<Base> to NULL

B.2.1.2): Construct the auto_ptr_ref. Called by the conversion operator:
> auto_ptr_ref(U* p)

B.2.2): Now call the constructor for the (also temporary) object needed
as function argument:
> auto_ptr(auto_ptr_ref<T> ref)

B.3 [**] Copy initialization is done. Destroy temporaries.

B.3.1): Destroy the auto_ptr_ref<Base>
> ~auto_ptr_ref()

B.3.2):  Destroy the first temporary auto_ptr<Base>
> Destructor

C. Bind the second temporary auto_ptr<Base> to the const reference and
excute the body of convert.

C.1): Call operator -> and then getX():
> operator->()

C.2): return

D): On return from convert(), destroy the temporary auto_ptr<Base> needed
for the argument. This finally destroys the Derived object (supposing a
virtual d'tor was added):
> Destructor

E): At the end of main() destroy the original auto_ptr<Derived>
> Destructor

<NOTE topic = "[*] and [**]">
Actually there are two copy initializations of an auto_ptr<Base> from an
auto_ptr<Base>:
- One serves to return the explicitly constructed result from the first
conversion operator function.
- The other one initializes the 'convert()' argument temporary from that
return value.

Obviously one of these is elided. The other one starts at [*].

AFAICS only the function return (which occurs within operator
auto_ptr<Base>) and not the original copy initialization (which occurs in
main()) allows destructing the temporaries used as early as [**].
The numbering at [*] and [**] might not reflect this accurately.
</NOTE>

- END discussion of program output ---------------------------

> It seems to me as my compiler makes three(?) implicite type conversions.
> I've learned that it is allowed to do only one. Am I wrong?

The rule is: Only one user-defined type conversion is allowed in one
conversion sequence.

If you followed my description, it makes two implicit type conversions
(and one constructor call) for initializing the temporary.

Those two type conversions are considered for separate purposes, i.e.
they are part of different conversion sequences. One conversion is needed
to start copy-initialization. The other one is the used to initialize a
constructor argument.

So much about sophistry ;-)

> Further the implementation seems to ignore that I'm passing a reference
> ( operator auto_ptr<U> calls release!!) which explains the strange
> behaviour in my first question. Is this right?

As I said before auto_ptr<Derived> and auto_ptr<Base> are unrelated classes.
Therefore the reference can't be bound directly.

> Finally, how on earth does this works. Or, how _should_ this work? Can
> someone explain that to me?

HTH.
But as that someone said: You don't really want to know.

> Thanks, Pelle

-- J   rg



      [ Send an empty e-mail to c++-help@netlab.cs.rpi.edu for info ]
      [ about comp.lang.c++.moderated. First time posters: do this! ]

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]