8

Let Base and Derived be classes with data members:

class Base {
public:
    Base(int i):f(i)     { }
    virtual void print() { cout << "base " << f << endl; }
    int f;
};

class Derived: public Base {
public:
    Derived(int i):Base(0),g(i) {  }
    void print() { cout << "derived " << g << endl; }
    int g;
};

Now create some instances of Base and Derived on the heap and store them in a boost::ptr_vector:

int main(int argc, char *argv[])
{
    boost::ptr_vector<Base> v;
    v.push_back(new Derived(1));
    v.push_back(new Base(2));
    v.push_back(new Base(3));
    v.push_back(new Derived(4));

print all the objects:

    for (std::size_t i(0); i != v.size(); ++i)
        v[i].print();

then reverse and print again:

    std::reverse(v.begin(), v.end());
    for (std::size_t i(0); i != v.size(); ++i)
        v[i].print();
}

This program prints:

derived 1
base 2
base 3
derived 4
derived 1
base 3
base 2
derived 4

std::reverse() on boost::ptr_vector calls std::iter_swap() which in turn calls std::swap which swaps items by creating a temporary copy.

However, the temporary copy does slice the Derived objects. As you can see, the int g of the Derived objects 1 and 4 are not swapped, so the objects are broken after the swap.

This behavior puzzles me. Isn't boost::ptr_vector a container to avoid exactly this kind of problem?

What I'd need here is a virtual copy constructor, which doesn't exist in C++.

How can I work around this? Do I need to implement a virtual swap function for Base, so that virtual dispatching calls another swap member function of Derived?

EDIT: Why copy objects in the first place? As the vector only stores pointers, reversing it should be a swap of the pointers, not the objects pointed to! Is there a function that does that?

Philipp
  • 957
  • 1
  • 6
  • 20
  • According to the FAQ for Ptr Containers for which mutating algorithms are okay, "Any mutating algorithm that moves elements around by swapping them." – chris Jul 19 '14 at 15:40
  • 1
    yes, but the elements of a ptr_vector are pointers, not objects. So it should be swapping the pointers, instead of swapping and slicing objects. – Philipp Jul 19 '14 at 16:07
  • It gets better: http://www.boost.org/doc/libs/1_55_0/libs/ptr_container/doc/tutorial.html#algorithms says 'algorithms are available as member functions'. Indeed, there's a boost::ptr_vector::reverse(). But when I call this, I get a compile error: 'No member named 'reverse' in 'std::vector >' – Philipp Jul 19 '14 at 16:31
  • The "virtual copy constructor" is handled in ptr_container with the "clonable" concept -- nonmember `new_clone` and `delete_clone` http://www.boost.org/doc/libs/1_55_0/libs/ptr_container/doc/reference.html#the-clonable-concept – Billy ONeal Jul 19 '14 at 16:37
  • 2
    Consider `vector>` instead. – Billy ONeal Jul 19 '14 at 16:39
  • I second Billy ONeal's suggestion. Here (http://stackoverflow.com/a/9472379/576911) is a rather long winded rationale for why. – Howard Hinnant Jul 19 '14 at 22:16

2 Answers2

1

Boost's ptr_vector class contains member functions whose job is manipulate the underlying pointers. But the public interface largely hides the fact that the vector internally stores pointers. The idea is to produce something that behaves as if it contained the objects directly but internally stores pointers to avoid slicing when you put an object into the container. But if you copy an object out of the container into an object of the base type, then it will slice. So you must not call such functions on ptr_vectors.

This means, unfortunately, that before you apply any algorithm to a ptr_vector, you need to understand exactly what it does.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
0

One possible work around is to use

std::reverse(v.base().begin(), v.base().end());

instead. It reverses the pointers instead of the self-indirecting iterators.

Philipp
  • 957
  • 1
  • 6
  • 20