C++: Why is std::vector(std::auto_ptr(MYTYPE)) bad?

Started by
12 comments, last by yacwroy 15 years, 9 months ago
I have just been converting much of my code to RAII. However, in a few places I have vectors of pointers that I would like to convert to auto_ptr. std::vector<std::auto_ptr<MYTYPE> > is exactly what I would want, except that I hear it doesn't work because std::auto_ptr doesn't fit the requirements for std::vector. AFAIK, the one requirement it doesn't meet is that it doesn't have a standard copy constructor (which requires that the copied item is const). To me it looks like it _should_ fit the requirements as long as I promise never to copy it, which I don't plan on doing. I understand that during inserts, erases and sometimes during resizes that the location of members in RAM change, which requires copy-constructing - but, shouldn't std::vector should be happy to use a non-const copy-constructor here since it's really a move or copy-construct + delete, hence the member being copied is already being treated as mutable. This would make std::vector<std::auto_ptr<MYTYPE> > correct for use in most applications, but still make copying it illegal. Secondly, if I can't use std::vector<std::auto_ptr>, is there a simple alternative? Thanks for all replies.
Advertisement
You may not copy it yourself, but it may get copied quite alot inside std::vector, or any other std:: container for that matter. You can use boost::shared_pointer.
Quote:Original post by yacwroy
I understand that during inserts, erases and sometimes during resizes that the location of members in RAM change, which requires copy-constructing - but, shouldn't std::vector should be happy to use a non-const copy-constructor here since it's really a move or copy-construct + delete, hence the member being copied is already being treated as mutable.


A non-const copy-constructor isn't a copy-constructor. It's just a constructor (or perhaps a movement one). For reasons related to sloppy standards and annoying language semantics, std::vector implementations are allowed to rely on the invariant that a copy of the object can be made—which implies that the original and the copy are equally valid. In the case of std::auto_ptr, if the vector created a 'copy', then decided to dump the 'copy' and keep the original for whatever reason, the pointer would be destroyed and you'd lose data.
To expand on the copying by std::vector: initially the vector will internally contain an array of objects with a certain length N. The moment you call push_back for the (N+1)th time the vector will allocate a new larger array, and copy everything from the first array to the new one. Since you don't know N, you cannot now when the copy constructors will get called. You could get around this by using the reserve() method of std::vector. But the suggestion to use shared_ptr is by far superior :)
Quote:Original post by yacwroy
To me it looks like it _should_ fit the requirements as long as I promise never to copy it, which I don't plan on doing.

Ignore the previous posts: vector<auto_ptr> will do exactly the right thing on insertion, the copying on expansion will do the right thing, and all that. There's not problem there.

The problem is that operations on the vector may make temporary copies of elements of the vector (for example, std::sort, which takes a copy of an element as a pivot value then discards it). Next thing you know, you've lost data. To this end, the ISO C++ committee decided to break auto_ptr by making its copy constructor take a non-const reference, where standard containers have a const reference. The idea is that it will not compile, just in case you might want to perform an operation that might be unsafe.

The right thing to do is to use a refcounted pointer, not auto_ptr, with a standard library container.

Stephen M. Webb
Professional Free Software Developer

Quote:Original post by Bregma
The right thing to do is to use a refcounted pointer, not auto_ptr, with a standard library container.

Or use a boost pointer container.
Quote:Original post by Bregma
Ignore the previous posts: vector<auto_ptr> will do exactly the right thing on insertion, the copying on expansion will do the right thing, and all that.

Ah yes, you are right. I've never even considered this. Maybe I should start thinking a bit more ;-)

Thanks.

shared_ptr is a good suggestion. Unfortunately, I'm not really a fan of shared_ptr though, and it's way too heavy for this use (not that that's too important).

I guess I better stay compliant and not just hope that a vector<auto_ptr> works. Thanks for confirming that it's illegal.

I've come up with a sort-of alternative, write my own template which encapsulates std::vector<T*> and calls delete on all pointers upon destruction.

template <typename T>class my::auto_vector {  public:    auto_vector() {}    ~auto_vector() {        for(std::vector<T*>::iterator it = m_items.begin(); it = m_items.end(); it++) {            delete (*it);        }    }    std::vector<T*> m_items;};


I guess I could also specify the std::vector functions that would be appropriate within auto_vector, and make m_items private. But it'll do for now.

I saw someone suggesting adding auto_vector to ::std
http://www.relisoft.com/resource/auto_vector.html
Not sure what is happening with this though.

Actually, I think I'll use that implementation eventually.
One more thing I just thought of:

Even though when objects are to be moved by std::vector (because of insert / erase or resizing) they are mutable, they will still need to be copied with a const copy-constructor so that if the copy-constructor throws partway through the operation, the vector's original contents are still ok, and RAII is done / leaks & corruption are prevented.

Of course, this would require that erase / insert operations relocate the entire vector, which is still linear but worst-case (and worse than the specs at www.cplusplus.com state - don't know where the official ones are).
Quote:Original post by yacwroyUnfortunately, I'm not really a fan of shared_ptr though, and it's way too heavy for this use (not that that's too important).

I'm sorry, that doesn't make sense.
What's heavy about shared_ptr?
And you don't need to be a "fan", you just have to realize that it's a good solution to the problem.

Moreover, instead of reinventing the wheel, you could just use boost's pointer container, as already suggested. it does pretty much what you were planning to do, but is much more likely to be 1) correct, 2) efficient.

This topic is closed to new replies.

Advertisement