Reputation: 4459
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
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
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
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
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
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
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