views:

461

answers:

3

Update: Edited code example to use AutoA for the workaround (which was the original intention). Realized this after seeing rlbond's answer.

I am trying to incorporate the usage of auto_ptr in my code based on recommendations from this thread:

http://stackoverflow.com/questions/478482/express-the-usage-of-c-arguments-through-method-interfaces/478678

However, I am receiving some unexpected compile errors when compiling with Visual Studio 6.0. It has a problem when dealing with assignments/copies of a std::auto_ptr of a derived type to a std::auto_ptr of the base type. Is this an issue specific to my compiler?

I know there's a strong recommendation to use Boost, but on my project it is not an option. If I still want to use auto_ptr, am I forced to use the workaround of calling std::auto_ptr::release()? From what I have encountered so far, this issue results in a compiler error, so it's easy enough to catch. However, could adopting the convention of calling release to assign to a 'auto_ptr' of base type throughout expose me to any maintenance issues? Especially if built with a different compiler (assuming other compilers don't have this issue).

If the release() workaround is not good due to my circumstances, should I fall back on using a different convention for describing transfer of ownership?

The following is an example illustrating the problem.

#include "stdafx.h"
#include <memory>

struct A
{
    int x;
};

struct B : public A
{
    int y;
};

typedef std::auto_ptr<A> AutoA;
typedef std::auto_ptr<B> AutoB;

void sink(AutoA a)
{
    //Some Code....
}

int main(int argc, char* argv[])
{
    //Raws to auto ptr
    AutoA a_raw_to_a_auto(new A());
    AutoB b_raw_to_b_auto(new B());
    AutoA b_raw_to_a_auto(new B());

    //autos to same type autos
    AutoA a_auto_to_a_auto(a_raw_to_a_auto);
    AutoB b_auto_to_b_auto(b_raw_to_b_auto);

    //raw derive to auto base
    AutoB b_auto(new B());

    //auto derive to auto base
    AutoA b_auto_to_a_auto(b_auto);  //fails to compile

    //workaround to avoid compile error.
    AutoB b_workaround(new B());
    AutoA b_auto_to_a_auto_workaround(b_workaround.release());

    sink(a_raw_to_a_auto);
    sink(b_raw_to_b_auto);  //fails to compile

    return 0;
}

Compile Error:

Compiling...
Sandbox.cpp
C:\Program Files\Microsoft Visual Studio\MyProjects\Sandbox\Sandbox.cpp(40) : error C2664: '__thiscall std::auto_ptr<struct A>::std::auto_ptr<struct A>(struct A *)' : cannot convert parameter 1 from 'class std::auto_ptr<struct B>' to 'struct A *'
        No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
C:\Program Files\Microsoft Visual Studio\MyProjects\Sandbox\Sandbox.cpp(47) : error C2664: 'sink' : cannot convert parameter 1 from 'class std::auto_ptr<struct B>' to 'class std::auto_ptr<struct A>'
        No constructor could take the source type, or constructor overload resolution was ambiguous
Error executing cl.exe.

Sandbox.exe - 2 error(s), 0 warning(s)
+4  A: 
AutoA b_auto_to_a_auto(b_auto);  //fails to compile

sink(b_raw_to_b_auto);  //fails to compile

Pavel Minaev points out something I actually didn't know:

The first call should compile, because there is an implicit conversion from a B* to an A*. However, the second will not compile. The following will:

sink(static_cast<AutoA>(b_raw_to_b_auto));

VC6 is notorious for not being very good with templates.

I highly suggest you upgrade your code base to one that actually works and begin using RAII techniques, especially boost::shared_ptr. I know you say you can't, and I know it's difficult, but you will have virtually no memory leaks and many, many fewer bugs.

Then again, maybe even without full functionality you can use auto_ptr?

rlbond
My understanding of auto_ptr was that it was intended to have similar semantics as a regular pointer. For example, if I had a function ofvoid sink(A* a);I would be able to make a call such as this:B* b = new B();sink(b);If this is a limitation that cannot be avoided when using std::auto_ptr, then I will have to reconsider it's usefuleness for what I am trying to do or my approach. Your comment against sink is actually the desired behavior in my case, as I am trying to transfer ownership of the object pointed to by the passed in auto_ptr.
FP
All I am saying is that exactly one auto_ptr is capable of owning any one object, and that assignment or copying transfers ownership. Many people believe that auto_ptr is a bad design because of this. For example, auto_ptr can not be used in STL containers.As long as you know what you're doing, it's ok, but assignment does not work like a regular pointer. I know you said boost is not an option, but boost's shared_ptr is really what you want if you need shared ownership.
rlbond
"They're unrelated types, despite A and B being related." and "If you want to treat A and B polymorphically, just use an auto_ptr<A>."Based on this, I'm going to assume that boost::shared_ptr would have the same issue?
FP
It's just wrong - `auto_ptr` is specifically carefully designed to implicitly convert from `auto_ptr<Derived>` to `auto_ptr<Based>` (look up `auto_ptr_ref` if you want to know the details). Ditto for `shared_ptr`. If it fails, it's only because the compiler isn't ISO-compliant, which is no surprise if we speak of VC6.
Pavel Minaev
More specifically, see mine and mmutz answers below. First line should compile fine but doesn't; second line shouldn't compile, but not for the reason you give.
Pavel Minaev
@Pavel Minaev: Wow, I didn't know that. Answer is edited. Thanks and I'll be sure to upvote your answer.
rlbond
+5  A: 

The first one is easy:

AutoA b_auto_to_a_auto(b_auto);  //fails to compile

This fails on VC6 since it requires member function templates, something VC6's standard library doesn't support. It compiles on standard-compliant compilers, though.

Workaround:

AutoA b_auto_to_a_auto( b_auto.release() );

The second one is much more subtle :)

sink(b_raw_to_b_auto);  //fails to compile

This one shouldn't compile on a standards-compliant compiler, because there's an implicit conversion going on. The compiler turns the above into

sink( std::auto_ptr<A>( b_raw_to_b_auto ) );

however, sink takes std::auto_ptr<A> by value, so the temporary std::auto_ptr<A> created implicitly by the compiler needs to be copy-constructed into the argument to sink. Now, temporaries like that are rvalues. Rvalues don't bind to non-const references, but std::auto_ptr's "copy constructor" takes it's argument by non-const reference.

There you go - compile error. AFAICS this is standards-conforming behaviour. C++-0x "move semantics" will fix that by adding a "copy constructor" that takes an rvalue reference, though I'm not sure how much love std::auto_ptr will still receive in the future, what with std::shared_ptr and all.

Workaround for the second one:

AutoA tmp( b_raw_to_b_auto/*.release() for VC6*/ );
sink( tmp );
Thanks for the answer. I had a feeling it had to do with VC6's templates, I've had issues with it before. I was expecting it was all a problem with VC6, interesting to find out the reasons why not. I didn't know about that rvalues detail, that's good info to know. After getting home, I was able to get the same build results matching what you say conforms to the standard. Thanks everyone else for your replies.
FP
+2  A: 

There are two issues here. First of all, this:

AutoA b_auto_to_a_auto(b_auto);

It is perfectly standard compliant and should compile. Let me explain why. ISO C++ standard specifies (20.4.5.1[lib.auto.ptr.cons]/4-6) the following constructor for auto_ptr<X> (among others);

template<class Y> auto_ptr(auto_ptr<Y>&) throw();

Note that Y is a different type from X here. And the Standard furthermore says:

Requires: Y* can be implicitly converted to X*.

The only thing to pay attention to here is that constructor argument is a reference-to-non-const. For your case, this isn't a problem (since you're passing a non-const variable there), but it becomes important for the next part. To conclude here: what you're seeing is non-standard behavior in VC6. It should compile on a compliant compiler (and will compile on VC7 and above). Now on to the second thing:

sink(b_raw_to_b_auto);  //fails to compile

This one is actually perfectly explained by mmutz, so I won't go into details here - see his answer. To conclude on that: yes, this line shouldn't compile, and won't in a compliant compiler (or VC6, as you've found out).

Pavel Minaev