CXJ
CXJ

Reputation: 4459

C++ How to call common method from classes without a shared superclass?

The following code gives a cross/jump label initialization compile errors, of course. But how do I get the effect I'm trying to achieve? That is, only instantiating the class I really need, and then generically calling the method which is common to all classes?

Class A and class B are actually not in my code, but in a large library I'm using, so can't be changed to help. They are NOT children of a superclass (which would solve the problem).

Both real classes handle similar data, so are compatible in the way illustrated below with the filter() method. I know a number of ugly C hacks which might be used to make it work, but I am looking for C++ idiomatic solutions.

In the real problem, there is a lot more code and many more cases, and the constructor and class methods are resource intensive, so I can't just init all possible classes "just in case" and then pick the right filter() method with the switch ().

#include <string>
#include <iostream>
class A {
public:
    std::string msg;
    A(std::string s) { msg = s;}
    void filter() { std::cout << "Message A = " << msg << std::endl;}
};

class B {
public:
    std::string msg;
    B(std::string s) { msg = s;}
    void filter() { std::cout << "The B message: " << msg << std::endl;}
};

int main() {
    int type = 1;
    switch (type) {
    case 1:
        A f("hi from A");
        break;
    case 2:
        B f("hello from B");
        break;
    }
    f.filter();
}

EDIT: Based on @stefan's answer, I revised my code to look like what's below. I haven't tried it in the real situation yet, but I believe it will work. (Thanks all!)

#include <string>
#include <iostream>
class A {
public:
    std::string msg;
    A(std::string s) { msg = s;}
    void filter() { std::cout << "Message A = " << msg << std::endl;}
};

class B {
public:
    std::string msg;
    B(std::string s) { msg = s;}
    void filter() { std::cout << "The B message: " << msg << std::endl;}
};

template <class F>
void doFilterStuff(std::string msg) {
    F f(msg);
    f.filter();
}

int main() {
    for (int i=1; i<4; i++) {
        std::cout << "Type = " << i << std::endl;
        switch (i) {
        case 1:
            doFilterStuff<A>("hi from A");
            break;
        case 2:
            doFilterStuff<B>("hello from B");
            break;
        default:
            std::cout << "Throwing an error exception" << std::endl;
        }
    }
}

Upvotes: 1

Views: 1879

Answers (6)

Artur Bac
Artur Bac

Reputation: 135

Aassuming You have objects of A or B already somewehere and theirs destruction is not the case, in C++11 You can use std::function and std::bind

#include <string>
#include <iostream>
#include <functional>

struct A {
    std::string msg;
    A(std::string const & s)  : msg(s) {}
    void filter() { std::cout << "Message A = " << msg << std::endl;}
};

struct B {
    std::string msg;
    B(std::string s) : msg(s) {}
    void filter() { std::cout << "The B message: " << msg << std::endl;}
};

int main() {
    int type = 1;
    //assuming You have objects already somewehere
    A a("hi from A");
    B b("hello from B");

    std::function<void()> filter_func;
    switch (type) {
    case 1:
        filter_func = std::bind( &A::filter, &a );
        break;
    case 2:
        filter_func = std::bind( &B::filter, &b );
        break;
    default:
      throw "Missing case";
    }
    filter_func();
}

Upvotes: 1

moonshadow
moonshadow

Reputation: 89115

This works, though is somewhat nasty:

#include <string>
#include <iostream>
class A {
public:
    std::string msg;
    A(std::string s) { msg = s;}
    void filter() { std::cout << "Message A = " << msg << std::endl;}
};

class B {
public:
    std::string msg;
    B(std::string s) { msg = s;}
    void filter() { std::cout << "The B message: " << msg << std::endl;}
};

// -------------

class Base
{
public:
  virtual void filter() = 0;
  virtual ~Base() {}
};

template<class C>
class Wrapper: public Base
{
public:
    Wrapper( C * impl ): m_impl(impl)   { }
    ~Wrapper()                          { delete m_impl; }

    virtual void filter()
    {
        m_impl->filter();
    }

private:
    C * m_impl;
};


// -------------

int main() {
    Base * f = NULL;

    int type = 1;
    switch (type) {
    case 1:
        f = new Wrapper<A>(new A("hi from A"));
        break;
    case 2:
        f = new Wrapper<B>(new B("hello from B"));
        break;
    }
    f->filter();
    delete f;
}

And the C++11, exception-safe variant with perfect forwarding of the constructors. Just Wrapper and main() are different from above, here they are:

template<typename T>
class Wrapper : public Base
{
public:
    template<typename... Args>
    Wrapper(Args&&... args) : m_impl(std::forward<Args>(args)...) {}

    virtual void filter() {
        m_impl.filter();
    }

private:
    T m_impl;
};

// -------------

int main()
{
    std::unique_ptr<Base> f;

    int type = 1;
    switch (type) {
    case 1:
        f.reset(new Wrapper<A>("hi from A"));
        break;
    case 2:
        f.reset(new Wrapper<B>("hello from B"));
        break;
    }
    f->filter();
}

Upvotes: 5

stefan
stefan

Reputation: 10355

Using templates can solve this:

template <class F>
void doFilterStuff()
{
   F f("Your string");
   f.filter();
}

int main()
{
   doFilterStuff<A>();
   doFilterStuff<B>();
}

Benefit from this: Less code, more abstraction, no boilerplate code. The compiler checks if all instantiations of the templated method are compatible: E.g. an instantiation with class C which does not provide the filter-method would result in a compile time error.

Templates are made for what this problem is all about: to provide same functionality for unconnected types which expose at least in part the same interface.

@NikBougalis correctly points out in the comments, that if you need to call special methods for each type, things get a bit ugly with templates, however it's perfectly feasible. Sample code is a bit too long, so I created this demo.

Upvotes: 4

nosleduc
nosleduc

Reputation: 103

In your example f is a local variable.

You can try something like this

class Base{
public :
 virtual ~Base(){}
 virtual void filter(){}
};

class A : public Base{
public :
A(const char * str){}
void filter(){}
};

class B : public Base{
public :
B(const char * str){}
void filter(){}
};

int main(){
int type = 1;
Base *f = 0;
switch (type) {
case 1:
    f = new A("hi from A");
    break;
case 2:
    f = new B("hello from B");
    break;
}
if (f)
f->filter();

}

If you can't get a common base class, you have to use a wrapper

Upvotes: -3

Nik Bougalis
Nik Bougalis

Reputation: 10613

So the idea is this: We define a new class AB, which has an "interface" similar to the interface we expecte from A and B. This new class internally contains pointers to A and B which are dynamically allocated - this allows us to define the "identity" of the object at runtime by creating an instance of this new class, and instructing AB to construct either an A or a B, as necessary.

All accesses to A and B are through the interface that AB exposes.

class AB {
    A *a;
    B *b;

public:
    AB()
        : a(nullptr), b(nullptr)
    { }

    ~AB() 
    {
        delete a;
        delete b;
    }

    void CreateA(std::string s)
    {
        if((a != NULL) || (b != NULL))
            return;

        a = new A(s);
    }

    void CreateB(std::string s)
    {
        if((a != NULL) || (b != NULL))
            return;

        b = new B(s);
    }

    void filter() 
    { 
        if(a)
        {
            a->filter();
            return;   
        }

        if(b) 
        {
            b->filter();
            return;
        }
    }
};



int main() {
    int type = 1;

    AB ab;

    switch (type) {
    case 1:
        ab.CreateA("hi from A");
        break;
    case 2:
        ab.CreateB("hello from B");
        break;
    }

    ab.filter();           
}

Upvotes: 2

Mats Petersson
Mats Petersson

Reputation: 129454

Actually, that is not doing what you expect, you are using two different (unrelated) classes to to produce f, and then calling f.filter() - which will always use the B variant, except sometimes it won't be initialized.

You can fix it by moving the f.filter into each case, and wrapping with braces:

switch (type) {
case 1:
    {
      A f("hi from A");
      f.filter();
    }
    break;
case 2:
    {
      B f("hello from B");
      f.filter();
    }
    break;
}

This will do what you expect.

Another option is to make a base-class with a virtual filter function and derive from that. Then use a pointer to the class, and call new to create the A or B class. Something like this:

class Base
{
public:
    std::string msg;
    virtual void filter() = 0;
};       

class A: public Base {
public:
    A(std::string s) { msg = s;}
    void filter() { std::cout << "Message A = " << msg << std::endl;}
};

class B : public Base {
public:
    B(std::string s) { msg = s;}
    void filter() { std::cout << "The B message: " << msg << std::endl;}
};

int main() {
    int type = 1;
    Base *f = NULL;
    switch (type) {
    case 1:
        f = new A("hi from A");
        break;
    case 2:
        f = new B("hello from B");
        break;
    }
    if (f)
      f->filter();
    else
       cout << "Huh? Didn't set 'f', bad type?" << endl;      

    delete f;
}

Upvotes: -1

Related Questions