0

As a followup to Class containing auto_ptr stored in vector, I believe the unstated conclusion there is that it is OK to use classes with an auto_ptr member as container elements, so long as both copy construction and copy assignment are user-defined (so as not to invoke the auto_ptrs copy constructor and copy assignment). Is there anything about this that violates the Standard Library requirements?

Please tell me if there is anything wrong with the following, as I'd like to start doing it idiomatically:

#include <memory>
#include <algorithm>

class Y { /* ... */ };
class Z : public Y { /* ... */ };

class X {
    public:
        X() : ap(new Z()) {}
        X(const X& other) : ap(other.ap->clone()) {}
        X& operator=(X other) { swap(other); return *this; } // any problem with swap?
        void swap(X& other) { std::swap(ap, other.ap); }
        // note no destructor necessary
    private:
        std::auto_ptr<Y> ap; // Y provides clone()
};

Note that one thing I've realized is that with this approach, Y's class definition must be in scope for X's definition (as opposed to just forward-declared when a "raw" pointer to Y is used). This is because when the auto_ptr is instantiated, its destructor must call delete on a Y, and so Y cannot be an incomplete type. I suppose this has to be weighed against the value of RAII in the case that class X managed several resources (has several auto_ptr members). Any thoughts on this?

I think this technique is great overall, to eliminate a lot of tricky code for classes that may have to construct/destruct/copy potentially several resources (single responsibility principal). I'm just wondering if auto_ptr serves the purpose, or if it doesn't work here because of some subtle language/Standard Library requirement.

Please note that I'm aware of other types of smart pointers, but I'm implementing a library interface, and I don't want to impose any requirements for Boost or TR1 on my clients. Any lecture on the use of auto_ptr in general will be downvoted!

Community
  • 1
  • 1
Tabber33
  • 535
  • 3
  • 11
  • You're calling std::swap with only one argument there. And secondly, I think your code would cause infinite recursion because std::swap uses the assignment operator. – Timo Nov 23 '10 at 20:34
  • @Timo: That's not calling `std::swap()`, but `this->swap()`. @Tabber33: The `std::` prefix for calling `std::swap()` isn't necessary, since ADL will find it when using `std::auto_ptr` as an argument. Also, the common idiom is to be `using std::swap` and then call `swap` unqualified, in order to pick up any overloads for user-defined types. (Which aren't allowed to reside in `std`, since you must not add anything to `std`, except for full specializations.) – sbi Nov 23 '10 at 22:17
  • @sbi -- I knew that, but since it's irrelevant to my question, I didn't show the idiomatic usage. My question is about the idiomatic usage of auto_ptr as a data member. Thanks though. – Tabber33 Nov 23 '10 at 22:24

3 Answers3

1

I think you've implemented a cloning smart pointer on top of std::auto_ptr. I'm not sure this is worth the hassle, as you're not very far from implementing one on top of a dumb pointer.

But as far as I see there's no error in this. (Of course, saying so will make someone point out an obvious flaw 5mins after I post this...)

sbi
  • 219,715
  • 46
  • 258
  • 445
1

Using auto_ptr when the destructor is out of scope does cause undefined behavior. MSVC warns about it. GCC doesn't.

You can make it work, but only if the destructor is not defined as an inline function.

The unique_ptr and scoped_ptr fix this problem. At least with those you are prevented from writing undefined code by accident.

Zan Lynx
  • 53,022
  • 10
  • 79
  • 131
  • Why can't the destructor be defined inline? In my case `Y`'s destructor is `virtual` and its definition is in scope when the `auto_ptr` is instantiated. What is wrong with this? – Tabber33 Nov 23 '10 at 20:11
  • @Zan -- can you explain how example 4(b) here: http://www.gotw.ca/publications/using_auto_ptr_effectively.htm works? -- he explicitly states we don't need to declare/define a destructor. – Tabber33 Nov 23 '10 at 20:58
  • Are you sure there are rather not problems when the stored type is incomplete? – UncleBens Nov 23 '10 at 21:12
  • @UncleBens: That is what I meant by out of scope: an incomplete type. I may not have been clear. – Zan Lynx Nov 24 '10 at 04:51
  • @Tabber33: Read this http://www.eggheadcafe.com/software/aspnet/31875527/autoptr-compile-error.aspx and scroll down to Tamas Demjen. He gives a code example showing the problem. – Zan Lynx Nov 24 '10 at 04:56
  • @Zan: thanks very much -- great link. I think I understand all of this now. The bottom line is: by 17.4.3.6/2, using an incomplete type in **any** Standard Library template is UB -- end of story. – Tabber33 Nov 24 '10 at 15:07
  • @Zan: In my case, `Y`'s definition is in scope for `X`'s definition, so `Y` is complete and there is no issue, even though `X` doesn't have an explicitly-declared destructor, correct? Any time the compiler has to generate an implicit inline destructor for `X`, `Y`'s definition is available at that point. I'm convinced that as long as `Y` is complete, this usage of `auto_ptr` is fine. However, it technically violates the standard when `Y` is only forward declared, and hence cannot be used for PIMPL, even with the non-inline destructor "trick"... accepted your answer for the link, thanks. – Tabber33 Nov 24 '10 at 16:07
-1

What are you really trying to do? Seems like alot of unnecessary work if you just want to have a bunch of objects in a container. creating a temp object and having the object in the container take over ownership isn't a problem in my thinking

operator =() does not make sense to me, it is not = it is swap.

X a,b;
a=b
assert(a==b) // fail!

Your realisation is incorrect

Note that one thing I've realized is that with this approach, Y's class definition must be in scope for X's definition (as opposed to just forward-declared when a "raw" pointer to Y is used). This is because when the auto_ptr is instantiated, its destructor must call delete on a Y, and so Y cannot be an incomplete type.

if you define the destructor for X and implement in a CPP file you can quite legally do

header

class Y;
class X{
  virtual ~X();
  std::auto_ptr<Y> ap; 
};

body

class Y{};
~X(){}
Greg Domjan
  • 13,943
  • 6
  • 43
  • 59
  • 1
    With regards to `operator=` -- it is called the copy-and-swap idiom -- look it up. The swap is done with the temporary that gets copy-constructed for the pass-by-value parameter, not with the actual RHS object. Will try your trick to allow a forward-declaration of `Y`, but I'm thinking `Y`'s def must be in scope at the point of `auto_ptr`'s instantiation (which is `X`'s definition). – Tabber33 Nov 23 '10 at 20:15
  • Ok, it works. But is it legal? Doesn't a type have to be complete at the point of a template instantiation using that type? – Tabber33 Nov 23 '10 at 20:25
  • Ah, I missed the lack of reference& making it copy(clone)&swap and not just swap. – Greg Domjan Nov 23 '10 at 21:46
  • Yes it is legal, template instantiation happens when it gets used. Usage for the ~auto_ptr and therfore ~Y is in ~X – Greg Domjan Nov 23 '10 at 21:48
  • No, class template instantiation happens in the class definition for `X`. At that point, `Y` is incomplete, and this is not allowed by the standard (though it usually works from what I gather). – Tabber33 Nov 24 '10 at 18:29
  • @tabber33: So, where is the definition? The Declaration of X is in the header. The usage of the template destructor of auto_ptr is in ~X, the definition of ~X is in the body, in the body Y is defined. I'm just repeating myself. As far as I read we agree, but you say no. – Greg Domjan Nov 29 '10 at 16:14
  • The _definition_ of `X` is in the header. The compiler must instantiate the `auto_ptr` specialization as part of the definition of `X`. The size of `X` depends on the instantiated `auto_ptr`. At this point, `Y` is incomplete, and we have UB according to the standard. – Tabber33 Nov 30 '10 at 15:47
  • Sure, your definition of X is in the header. Mine was not, I only declared in the header and defined in the body. Which is what Zan your accepted answer says. Your realisation is still incorrect. – Greg Domjan Nov 30 '10 at 17:14
  • You need to understand what a **class definition** is -- look it up please. – Tabber33 Nov 30 '10 at 19:08