Tom de Geus
Tom de Geus

Reputation: 5985

Convert class to derived class, without modifying it

I am working with a set of classes A, B, ... These classes are independent except that they have one method in common. Now I want to combine these classes in a vector, to call method in one loop. It seems that the best solution is to make the classes derived classes from some Parent (see below).

Now the question is the following. I want to create a header-only library for each class (a.h, b.h, ...). There I want the classes to be completely independent. Only in the main module I want to 'attach' the classes to a Parent to be able to combine them in a vector. How do I do this? Or do I have to resort to a vector of void* pointers? Or is there another way to combine these classes in a vector?


Classes in list: with parent/child paradigm

Here is what I have been able to do to combine the classes in the vector. Note I specifically want to avoid the parent/child paradigm in the class definitions. But I still want to combine them in a vector.

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

class Parent
{
public:
  virtual ~Parent(){};
  virtual void method(){};
};

class A : public Parent
{
public:
  A(){};
  ~A(){};
  void method(){};
};

class B : public Parent
{
public:
  B(){};
  ~B(){};
  void method(){};
};

int main()
{
  std::vector<std::unique_ptr<Parent>> vec;

  vec.push_back(std::unique_ptr<Parent>(new A));
  vec.push_back(std::unique_ptr<Parent>(new A));
  vec.push_back(std::unique_ptr<Parent>(new B));

  for ( auto &i: vec )
    i->method();

  return 0;
}

Compile using e.g.

clang++ -std=c++14 main.cpp

Upvotes: 2

Views: 79

Answers (3)

skypjack
skypjack

Reputation: 50568

A possible solution based on type erasure, static member functions and pointers to void that doesn't make use of virtual at all (example code, far from being production-ready):

#include <iostream>
#include <vector>

struct Erased
{
    using fn_type = void(*)(void *);

    template<typename T>
    static void proto(void *ptr) {
        static_cast<T*>(ptr)->method();
    }

    fn_type method;
    void *ptr;
};

struct A
{
  void method(){ std::cout << "A" << std::endl; };
};

struct B
{
  void method(){ std::cout << "B" << std::endl; };
};

int main()
{
  std::vector<Erased> vec;

  vec.push_back(Erased{ &Erased::proto<A>, new A });
  vec.push_back(Erased{ &Erased::proto<B>, new B });

  for ( auto &erased: vec ) {
      erased.method(erased.ptr);
  }

  return 0;
}

This can help to avoid using a common base class. See it on wandbox.


As mentioned in the comments, here is a slightly modified version that adds create and invoke methods to reduce the boilerplate for the users.

Upvotes: 5

n. m. could be an AI
n. m. could be an AI

Reputation: 120239

This is more of a pseudocode, trivial details are omitted.

struct HolderBase
{
   virtual void foo() = 0;
};

template <class T>
struct Holder : HolderBase
{
  Holder(T* t) : t(t) {}
  T* t;
  void foo() { t->foo(); }
};

std::vector<HolderBase*> v { new Holder<A>(new A), new Holder<B>(new B) };

You can also have a variant of Holder that holds an object by value (and mix both variants in the same vector freely).

If you have a single method to call, there is a much simpler solution:

A a;
B b;
std::vector<std::function<void()> v { [](){a.foo();}, [](){b.foo();} };

Upvotes: 4

You want to erase the type of the objects and treat them uniformly, so naturally type erasure is the solution.

class with_method_t {
   struct model_t {
     virtual ~model_t() = default;
     virtual void call_method() = 0;
   };

   template<class C>
   class concept_t final : public model_t {
     C obj;
   public:
     concept_t(C const& c) : obj{c} {}
     concept_t(C&& c) : obj{std::move(c)} {}
     void call_method() override { obj.method(); }
   };

   std::unique_ptr<model_t> instance;
public:
   template<class C>
   with_method_t(C&& arg)
     : instance{std::make_unique<concept_t<C>>(std::forward<C>(arg))}
   {}

   void method() { instance->call_method(); }
};

Then have yourself a vector of with_method_t which is a value type. No raw dynamic allocation or de-allocation. The instance is build by forwarding the argument it receives into a small polymorphic container:

std::vector<with_method_t> vec;
vec.emplace_back(A{});
vec.emplace_back(B{});

for ( auto &i: vec )
  i.method();

Upvotes: 2

Related Questions