Passing std::vector of derived type where a std::vector of base type is expected

萝らか妹 提交于 2019-12-22 10:24:40

问题


I have a base class:

class Member
{
    ....
}

and a derived class, like this:

class Mine_sweeper : private Member
{
    ....
}

Then I have a container:

class Population
{
public:
    std::vector<Member>& m_members;
    Population(std::vector<Member>& members);
}

When I use Population's constructor with a vector of Members it compiles OK. But if I try to link Population's m_members with a vector of Mine_sweepers, complier throws an error.

std::vector<Member> members;
Population pop(members); // this compiles OK

std::vector<Mine_sweeper> sweepers;
Population pop(sweepers); // but here I get an error
main.cpp(23): error C2664: 'Population::Population(std::vector &)' : cannot convert parameter 1 from 'std::vector' to 'std::vector &'

回答1:


You can't. Not as is. This is because std::vector<B> is not derived from std::vector<A>, regardless of the fact that B is derived from A.

But wait ... let us simplify your problem with a minimal example:

struct A {};
struct B : A {};
void f(std::vector<A> const&) {}

int main()
{
    std::vector<A> vecA;
    std::vector<B> vecB;
    f(vecA);
    f(vecB); // error
}

Here, unlike in your sample code, B publicly inherits from A. You still can't give a std::vector<B> const& to a function expecting a std::vector<A> const& because there exists no implicit conversion from std::vector<B> to std::vector<A>.

In the following case, one can send a B const& where a A const& is expected because C++ provide an implicit conversion from reference to a derived type to a reference to a base type.

struct A {};
struct B : A {};
void f(A const&) {}

int main()
{
    A alice;
    B bob;
    f(alice);
    f(bob);
}

A solution is to provide an explicit conversion using the existing implicit conversion from B to A:

struct A {};
struct B : A {};
class Container
{
    std::vector<A*> _items;
public:
    template<class T>
    Container(std::vector<T> v)
        : _items(v.begin(), v.end())
    {}
    // other stuff, see rule of 5
};


int main()
{
    std::vector<A*> vecA;
    std::vector<B*> vecB;

    Container contA(vecA);
    Container contB(vecB);
}

Once you have a basic working conversion, you need to figure out how to handle memory ownership with std::unique_ptr, std::shared_ptr and std::weak_ptr.




回答2:


Vector of Mine_sweepers is not a "kind of" vector of Members, even when Mine_sweeper is a "kind of" Member. You can not use vector<Derived> in place of vector<Base>.

Use vector<unique_ptr<Member>> instead of vector<Member> AND vector<Mine_sweeper>. This enables you to have heterogeneous Population of Members (i.e. Mine_sweepers and other Member sub-types, at the same time).

Alternatively, convert the Population class to a class template:

class Population<T>
{
public:
    std::vector<T>& m_members;
    Population(std::vector<T>& members);
}

This way, one Population instance may only contain one type of Members.




回答3:


The constructor for the Population class takes an argument to a vector holding an array of Member class. You're right that you can pass a derived class to something that takes the base class as argument, or upcast from derived to base, but what you're doing here is different. You're not sending a derived class to a function taking a base class argument, you're sending a different unrelated type altogether, you're sending an std::vector< typeA> to something that wants std::vector< typeB>.

When you create std::vector< int> at compile time it will create the vector class with all the specific details specific to how it for example iterates over the ints etc. This is an array or "ints". Would it make sense to pass it to a function that takes an array of doubles? Then the function is doing work on doubles, for example to move to the next double it might move forward 8 bytes instead of 4, it would be doing the wrong thing.

You would have to make two versions of the Population class manually or use templates to do it. Edit: See YSC's answer, he templates the constructor

Or, you probably want one vector/array which could contain both types of class, Base and Minesweeper. To do this you can't store the objects themselves in the array, because the two types of object might be different size and have different members. So you can have a vector of pointers to the Base class. Then the polymorphism part is for identifying, when you dereference one of those pointers, whether you're pointing to a Base or a Minesweeper. The virtual function is for calling the right function depending on which object it is.




回答4:


You can't just pass a vector of derived class objects to a function who need a vector of of base class objects, because vector is not derived from vector.

You can do some covertion, get a slice of vector, like this:

vector<Base> converted(base_vec.begin(), base_vec.end());

But this is not efficient, if the calling method is in your control, you can just use template:

template <typename T>
void foo(const vector<T>& v) {
    v[0].bar(); // do something with v
}

If you do not want other types those have bar but is not the derived class of Base, you can use std::enable_if:

template<typename T, typename Enable=std::enable_if<std::is_base_of<Base, T>::value>>
void foo(const std::vector<T>& v) {
    v[0].bar(); // do something with v
}

Whole program can compile with gcc main.cc -std=c++11:

#include <vector>

class Base{
public:
    void bar() const {}
};

class Derived: public Base{
};

template<typename T, typename Enable=std::enable_if<std::is_base_of<T, Base>::value>>
void foo(const std::vector<T>& v) {
    v[0].bar();
}

int main() {
    std::vector<Derived> a(1);
    foo(a);
}


来源:https://stackoverflow.com/questions/40820183/passing-stdvector-of-derived-type-where-a-stdvector-of-base-type-is-expected

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!