0

I'm new to C++ and I'm stuck on the following.

Let's say we have a base class:

class A{

    public:
        virtual void myfunc(vector <A> *a){
            // nothing to do here
    }

};

And two subclasses:

class B : public A {
    public:
        void myfunc(vector <A> *a){
            //some other code
    }
};

class C : public A {
    public:
        void myfunc(vector <A> *a){
            //some other code
    }
};

Here's another class where I create misc functions:

class D {
    public:
        void dosomething(vector <A> * c){
           // i need to call upon polymorphism here so myfunc is dynamically called for B or C
    }

};

My problem lies in class D. How can I achieve polymorphism there? I've tried a lot, but I end up still calling the base class's function.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770

3 Answers3

2

In C++, polymorphism requires using a pointer or a reference to be able to access the polymorphic behavior. The pointer can be a smart pointer, like unique_ptr or shared_ptr.

With the vector<A>, that vector can only hold A objects. It cannot hold a B or C object.

Here's an example changing the parameter to vector<unique_ptr<A>>. (Also, passing a parameter by pointer indicates that a nullptr is an acceptable value unless indicated otherwise such as by a comment or an assert.)

#include <iostream>
#include <memory>
#include <string>
#include <vector>

using std::cout;
using std::make_unique;
using std::ostream;
using std::string;
using std::unique_ptr;
using std::vector;

namespace {

struct A {
    virtual ~A();
    virtual void myfunc(vector<unique_ptr<A>> *a) {
        if (a)
            cout << "A has a vector with " << a->size() << " objects\n";
        else
            cout << "A had a null vector\n";
    }
    virtual auto name() const -> string {
        return "base A";
    }
    virtual auto color() const -> string {
        return "azure";
    }
};

A::~A() = default;

struct B : A {
    void myfunc(vector<unique_ptr<A>>* a) override {
        if (a) {
            for (auto&& x : *a) {
                if (x)
                    cout << "B saw a " << x->name() << "\n";
                else
                    cout << "B saw a null pointer\n";
            }
        } else {
            cout << "B had a null vector\n";
        }
    }
    auto name() const -> string override {
        return "child B";
    }
    auto color() const -> string override {
        return "byzantine";
    }
};

struct C : A {
    void myfunc(vector<unique_ptr<A>>* a) override {
        if (a) {
            for (auto&& x : *a) {
                if (x)
                    cout << "C saw the color " << x->color() << "\n";
                else
                    cout << "C saw a null pointer\n";
            }
        } else {
            cout << "B had a null vector\n";
        }
    }
    auto name() const -> string override {
        return "child C";
    }
    auto color() const -> string override {
        return "cerulean";
    }
};

struct Ord {
    int nth = 0;
    auto operator++() -> Ord& { ++nth; return *this; }
    friend auto operator<<(ostream& out, Ord const& o) -> ostream& {
        out << o.nth;
        if (o.nth == 1) out << "st";
        else if (o.nth == 2) out << "nd";
        else if (o.nth == 3) out << "rd";
        else out << "th";
        return out;
    }
};

class D {
public:
    void dosomething(vector<unique_ptr<A>>* a){
        // i need to call upon polymorphism here so myfunc is dynamically called for B or C
        if (a) {
            Ord ord;
            for (auto&& x : *a) {
                cout << "D::dosomething with " << ++ord << "\n";

                if (x) {
                    x->myfunc(a);
                } else {
                    cout << "D had a null in the vector\n";
                }

                cout << "\n";
            }
        } else {
            cout << "D had a null vector\n";
        }
    }
};

} // anon

int main() {
    vector<unique_ptr<A>> a;
    a.push_back(make_unique<A>());
    a.push_back(make_unique<B>());
    a.push_back(make_unique<C>());
    a.push_back(unique_ptr<A>());
    a.push_back(make_unique<A>());
    D d;
    d.dosomething(&a);
}
Eljay
  • 4,648
  • 3
  • 16
  • 27
  • Thank you. your answer helped me understand why is wasn't working. I reviewed another answer and i clearly see that this is about object slicing. A subject i wasn't introduced so far. – sidiki camara Jun 27 '22 at 20:37
1

I'm not exactly sure this is what you're after, but here you have a small example including:

  • A hierarchy of classes A (base), and B, and C (derived). I've made A pure virtual.
  • B and C classes implement myfunc. B prints the int it holds, and C prints the char it holds.
  • A class D with a dosomething method that receives a vector of A*, and calls myfunc on each of them. Here's where runtime polymorphism kicks in. I've used std::unique_ptr<A> instead of A*.
  • I've also used structs instead of classes and changed myfunc's signature just to avoid some typing.

[Demo]

#include <fmt/core.h>
#include <memory>
#include <vector>

struct A {
    virtual void myfunc() const = 0;
};

struct B : public A {
    explicit B(int i) : i_{i} {}
    int i_{};
    virtual void myfunc() const override {
        fmt::print("B: {}\n", i_);
    }
};

struct C : public A {
    explicit C(char c) : c_{c} {}
    char c_{};
    virtual void myfunc() const override {
        fmt::print("C: {}\n", c_);
    }
};

struct D {
    static void dosomething(const std::vector<std::unique_ptr<A>>& v_a) {
        for (auto&& a : v_a) {
            a->myfunc();
        }
    }
};

int main() {
    std::vector<std::unique_ptr<A>> v{};
    v.emplace_back(std::make_unique<B>(555));
    v.emplace_back(std::make_unique<C>('f'));
    
    D::dosomething(v);
}

// Outputs:
//   B: 555
//   C: f
rturrado
  • 7,699
  • 6
  • 42
  • 62
0

Note that vector<B> is not a vector<A> simply based on B is an A. Therefore, you can either:

  • have a vector of pointers, i.e., vector<A*> in dosomething()
  • have a vector of variants, i.e. vector<variant<B, C>> if you're sure it's B or C, but then use variant interface in dosomething (likely visitors or std::get<>())
  • in the quite usual case when the order of B and C elements is not important in processing, have a tuple of vectors, tuple<vector<B>, vector<C>>. This is likely faster as there's no polymorphic overhead.
lorro
  • 10,687
  • 23
  • 36
  • 3
    `unique_ptr` or `shared_ptr` are better than raw pointers in the vector. Somebody has to delete the objects when the vector goes out of scope. – Goswin von Brederlow Jun 27 '22 at 17:45
  • 2
    A variant of vectors won't work here. A variant stores one or the other, not both at the same time. Perhaps you were thinking of a tuple? Polymorphic overhead is small - any performance gains from this approach are more likely to be from improved locality of reference. It does make the source code more cumbersome, though. – Avi Berger Jun 27 '22 at 17:54
  • @AviBerger yes, thanks for spotting it, fixed. Polimorphic overhead is quite high actually - yes, memory allocation/locality is higher by default allocator, but you can work around it via using your own allocator, something that many projects does. I don't think it's more cumbersome - you can write your own `foreach(tupleOfVectors& tvv, T lambda);` that calls lambda with static type references -, but you do lose relative order of elements across types. – lorro Jun 27 '22 at 18:34