Getting a vector<Derived*> into a function that expects a vector<Base*>
Asked Answered
A

9

28

Consider these classes.

class Base
{
   ...
};

class Derived : public Base
{
   ...
};

this function

void BaseFoo( std::vector<Base*>vec )
{
    ...
}

And finally my vector

std::vector<Derived*>derived;

I want to pass derived to function BaseFoo, but the compiler doesn't let me. How do I solve this, without copying the whole vector to a std::vector<Base*>?

Altman answered 22/9, 2008 at 13:21 Comment(1)
I think you need to clarify whether BaseFoo is intended to take the vector by const or non-const reference (it would be very unusual for it to take it by value). In other words, does BaseFoo need to modify the vector?Tobar
A
44

vector<Base*> and vector<Derived*> are unrelated types, so you can't do this. This is explained in the C++ FAQ here.

You need to change your variable from a vector<Derived*> to a vector<Base*> and insert Derived objects into it.

Also, to avoid copying the vector unnecessarily, you should pass it by const-reference, not by value:

void BaseFoo( const std::vector<Base*>& vec )
{
    ...
}

Finally, to avoid memory leaks, and make your code exception-safe, consider using a container designed to handle heap-allocated objects, e.g:

#include <boost/ptr_container/ptr_vector.hpp>
boost::ptr_vector<Base> vec;

Alternatively, change the vector to hold a smart pointer instead of using raw pointers:

#include <memory>
std::vector< std::shared_ptr<Base*> > vec;

or

#include <boost/shared_ptr.hpp>
std::vector< boost::shared_ptr<Base*> > vec;

In each case, you would need to modify your BaseFoo function accordingly.

Adduction answered 22/9, 2008 at 13:31 Comment(0)
D
27

Instead of passing the container object (vector<>), pass in begin and end iterators like the rest of the STL algorithms. The function that receives them will be templated, and it won't matter if you pass in Derived* or Base*.

Dinar answered 22/9, 2008 at 14:10 Comment(1)
+1: easiest thing to do without boost or shared pointer (that i don't know yet)Full
S
14

This problem occurs in programming languages that have mutable containers. You cannot pass around a mutable bag of apples as a bag of fruit because you cannot be sure that someone else does not put a lemon into that bag of fruit, after which it no longer qualifies as a bag of apples. If the bag of apples were not mutable, passing it around as a bag of fruit would be fine. Search for covariance/contravariance.

Songsongbird answered 22/9, 2008 at 17:6 Comment(0)
C
8

one option is to use a template

template<typename T>
void BaseFoo( const std::vector<T*>& vec)
{
 ...
}

The drawback is that the implementation has to be in the header and you will get a little code bloat. You will wind up with different functions being instantiated for each type, but the code stays the same. Depending on the use case it's a quick and dirty solution.

Edit, I should note the reason we need a template here is because we are trying to write the same code for unrelated types as noted by several other posters. Templates allow you do solve these exact problems. I also updated it to use a const reference. You should also pass "heavy" objects like a vector by const reference when you don't need a copy, which is basically always.

Celandine answered 22/9, 2008 at 13:23 Comment(2)
Potentially the compiler is allowed to optimize the two instances of the function into just one by realizing the object code generated is exactly the same. Doubt many would do so with a complicated function though.Scheming
You can avoid the need to define the function in the header file by using explicit instantiations. You might merge my example (below) into your answer.Abercrombie
S
2

Generally you would start with a container of base pointers, not the other way.

Scheming answered 22/9, 2008 at 13:27 Comment(1)
I think both cases do happen in practice. But I would agree a vector<Base*> is more common in good OO design.Celandine
G
2

If you dealing with a third-party library, and this is your only hope, then you can do this:

BaseFoo (*reinterpret_cast<std::vector<Base *> *>(&derived));

Otherwise fix your code with one of the other suggesstions.

Gawky answered 22/9, 2008 at 13:39 Comment(2)
While that might actually work, it feels like a ticking time bomb of code. I'm very hesitant to use reinterpret_cast unless I know exactly what I'm doing.Celandine
This is the only answer to the question. The other answers are basically "avoid that problem" - these are good advices, but not always applicable. It could be a little simpler: BaseFoo(reinterpret_cast<std::vector<Base*> &>(derived));Quasimodo
A
2

Taking Matt Price's answer from above, given that you know in advance what types you want to use with your function, you can declare the function template in the header file, and then add explicit instantiations for those types:

// BaseFoo.h
template<typename T>
void BaseFoo( const std::vector<T*>& vec);

// BaseFoo.cpp
template<typename T>
void BaseFoo( const std::vector<T*>& vec);
{
 ...
}

// Explicit instantiation means no need for definition in the header file.
template void BaseFoo<Base> ( const std::vector<Base*>& vec );
template void BaseFoo<Derived> ( const std::vector<Derived*>& vec );
Abercrombie answered 22/9, 2008 at 15:6 Comment(0)
C
1

If std::vector supported what you're asking for, then it would be possible to defeat the C++ type system without using any casts (edit: ChrisN's link to the C++ FAQ Lite talks about the same issue):

class Base {};
class Derived1 : public Base {};
class Derived2 : public Base {};

void pushStuff(std::vector<Base*>& vec) {
    vec.push_back(new Derived2);
    vec.push_back(new Base);
}

...
std::vector<Derived1*> vec;
pushStuff(vec); // Not legal
// Now vec contains a Derived2 and a Base!

Since your BaseFoo() function takes the vector by value, it cannot modify the original vector that you passed in, so what I wrote would not be possible. But if it takes a non-const reference and you use reinterpret_cast<std::vector<Base*>&>() to pass your std::vector<Derived*>, you might not get the result that you want, and your program might crash.

Java arrays support covariant subtyping, and this requires Java to do a runtime type check every time you store a value in an array. This too is undesirable.

Charlotte answered 22/9, 2008 at 15:0 Comment(0)
T
0

They are unrelated types -- you can't.

Tafoya answered 22/9, 2008 at 13:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.