Reputation: 987
I have a virtual class, named Type, and to derived classes, Type1 and Type2.
class Type {
public:
virtual void print() = 0;
};
class Type1 : public Type {
public:
void print() { cout << "I am of type 1" << endl; }
};
class Type2 : public Type {
public:
void print() { cout << "I am of type 2" << endl; }
};
Depending on parameters the user will enter, I will instantiate one class or the other.
Then I want to have another class that will produce actions, depending on the type of the "Type" object given as parameter. For now, this class is :
class Action {
protected:
Type *t;
public:
Action(Type *t) : t(t) {} ;
void print() {
cout << "I am an action. My type says: ";
t->print();
}
};
How can I have Action::print produce different tasks depending on the type of the attribute "t"?
What I tried:
My wish is to have a main that looks like:
int main(int argc, const char * argv[]) {
Type *t = new Type1();
Action *act = new Action(t);
act->print();
delete act, t;
return 0;
}
But with a different print method called depending on the type of t. Is this possible (and how) or not?
Edit
After reading the comments, it turns that there are two main possible designs:
Create one class, Action, and call the specific method (print(Type1 *) or print(Type2 *)). I think this is the visitor pattern suggested in the comments.
Create two classes, Action1 and Action2 (possibly derived from Action, or not!), and instantiate the one that corresponds to t, Type1 or Type2.
Is one solution cleaner, easier to maintain, etc., than the other?
Edit 2
What about a Factory pattern, mentioned in some comments? Can anyone enlighten me about it solves or not my problem?
Upvotes: 2
Views: 2166
Reputation: 31116
A small remark first: I personally consider 'using namespace' a bad practice, because it ripples through header files. The reason is described throughout the internet.
Also, I always attempt to minimize using pointers, because of exception safety. Sutter has a nice book about it, called Exceptional C++, which describes these problems in a lot of detail. However, pointers obviously have their uses and one in particular is polymorphism. Lastly, I like making classes struct's if they only have public members... which is just a matter of taste.
Let's start with some code:
#include <string>
#include <iostream>
#include <memory>
struct Type
{
virtual void print() = 0;
};
struct Type1 : Type
{
void print() { std::cout << "I am of type 1" << std::endl; }
};
struct Type2 : Type
{
void print() { std::cout << "I am of type 2" << std::endl; }
};
class Action
{
protected:
std::unique_ptr<Type> t;
public:
Action(std::unique_ptr<Type> &&t) : t(std::move(t)) {};
void print()
{
std::cout << "I am an action. My type says: ";
t->print();
}
};
int main()
{
Action act(std::make_unique<Type1>());
act.print();
return 0;
}
The first problem you seem to address is the fact that user input generates the types. Depending on the input, this could point to an abstract factory or builder pattern or even a full-blown parser, but if it's an easy input decision, best to KISS:
int main()
{
std::string s;
std::getline(std::cin, s);
std::unique_ptr<Type> type;
if (s == "1")
{
type = std::make_unique<Type1>();
}
else
{
type = std::make_unique<Type2>();
}
Action act(std::move(type));
act.print();
return 0;
}
Usually you want to separate your model from the implementations though. Actions come in a lot of different shapes and forms, so you probably want to do something else depending on your model. A visitor pattern, double dispatch, or whatever you want to call it comes to the rescue here.
Note that I normally use a somewhat modified visitor, which returns the base type as a default. That way, you can easily incorporate transformations into your code, which imho is a common design requirement. At this point I'm just going to use pointers. Personally I'm a big fan of memory arena's to work around the issues of memory management, but I consider this out of scope for this answer.
To finish things off, a simple base class can help you get rid of the senseless plumbing.
struct TypeVisitor;
struct Type
{
virtual Type* Accept(TypeVisitor& visitor) = 0;
};
template <typename T>
struct TypeBase : Type
{
virtual Type* Accept(TypeVisitor& visitor) override
{
return visitor.Handle(static_cast<T*>(this));
}
};
struct Type1 : TypeBase<Type1>
{
};
struct Type2 : TypeBase<Type2>
{
};
struct TypeVisitor
{
virtual Type* Handle(Type1* input)
{
/* if necessary, recurse here like: input->child = input->child->Accept(this); */
return input;
}
virtual Type* Handle(Type2* input) { return input; }
};
struct DumpAction : TypeVisitor
{
virtual Type* Handle(Type1* input) override
{
std::cout << "Handling type 1." << std::endl;
return TypeVisitor::Handle(input);
}
virtual Type* Handle(Type2* input) override
{
std::cout << "Handling type 2." << std::endl;
return TypeVisitor::Handle(input);
}
};
int main()
{
DumpAction act;
Type2 type2;
type2.Accept(act);
return 0;
}
Upvotes: 2
Reputation: 987
Here is what I came to.
#include <iostream>
using namespace std;
/** Class Type and its derived classes Type1 and Type2
*/
class Type {
public:
int type;
virtual void print() = 0;
};
class Type1 : public Type {
public:
Type1() { type = 1; }
void print() { cout << "I am of type 1" << endl; }
};
class Type2 : public Type {
public:
Type2() { type = 2; }
void print() { cout << "I am of type 2" << endl; }
};
/** Class Action and its derived classes Action1 and Action2
*/
class Action {
protected:
Type *t;
public:
Action(Type *t) : t(t) {};
static Action *make_action(Type *t);
virtual void print() = 0;
};
class Action1 : public Action {
public:
Action1(Type *t) : Action(t) {};
void print() {
cout << "I am an action1. My type says: ";
t->print();
}
};
class Action2 : public Action {
public:
Action2(Type *t) : Action(t) {};
void print() {
cout << "I am an action2. My type says: ";
t->print();
}
};
Action *Action::make_action(Type *t) {
switch(t->type) {
case 1:
return new Action1(t);
break;
case 2:
return new Action2(t);
break;
}
}
/** Main
*/
int main(int argc, const char * argv[]) {
Type *ta = new Type1();
Action *acta = Action::make_action(ta);
acta->print();
delete acta, ta;
Type *tb = new Type2();
Action *actb = Action::make_action(tb);
actb->print();
delete actb, tb;
return 0;
}
The output is:
I am an action1. My type says: I am of type 1
I am an action2. My type says: I am of type 2
It seems like it does the expected job. If anyone sees a mistake, please let me know.
Upvotes: 0
Reputation: 25613
Using mixing you can write your classes very natural. You only extend the CRTP pattern to make them able to use mixin technique later. The code is simpler than any textual explanation, so please look yourself:
#include <iostream>
#include <memory>
class Type
{
public:
virtual void Do()=0;
};
template <typename Base>
class Type1: public Base
{
void Do() override { std::cout << "Type1::Do" << std::endl; }
};
template <typename Base>
class Type2: public Base
{
void Do() override { std::cout << "Type2::Do" << std::endl; }
};
template <typename Base>
class Action: public Base
{
public:
virtual void Print()=0;
};
template <typename Base>
class Action1: public Base
{
void Print() override { std::cout << "Print for Action1" << std::endl;}
};
template <typename Base>
class Action2: public Base
{
void Print() override { std::cout << "Print for Action2" << std::endl;}
};
int main()
{
// what we want "mixin" is Action into Type
using Base = Action<Type>;
std::unique_ptr<Base> a1 { new Action1<Type1<Base>> }; // Action1 in Type1
std::unique_ptr<Base> a2 { new Action2<Type2<Base>> }; // Action2 in Type2
a1->Do();
a1->Print();
a2->Do();
a2->Print();
}
Upvotes: 1
Reputation: 5510
While inheritance-based polymorphism does have its uses, this doesn't sound like one of them. Generally, in an inheritance-based design, the task performed should only depend on the behavior of the involved classes, not on their concrete type.
A way around that limitation of inheritance is to use the Visitor pattern, which essentially exposes the concrete type of a class in the inheritance hierarchy through how it reacts to a visitation request.
The one use-case where you can't get around a inheritance-based design with Visitor pattern is when users whose code-base you cannot control might add additional derived classes. But as far as I can tell this isn't a concern here.
So the other way to get this to work is through variant
instead of inheritance (I'm using c++17 std::variant
s here, but you can do the same with Boost.Variant if you don't have access to a compiler/STL that already implements std::variant
.) Just define your types as
class Type1 {
public:
void print() const {
std::cout << "I am of type 1\n";
}
};
class Type2 {
public:
void print() const {
std::cout << "I am of type 2\n";
}
};
and define the "base type" with a using
-declaration
using Type = std::variant<Type1, Type2>;
The action itself would look like this:
class Action {
Type t_;
public:
Action(Type t)
: t_(std::move(t)) {
}
void print() {
auto action = make_combined(
[](Type1 const& t) { std::cout << "Called with argument of type Type1\n"; t.print(); },
[](Type2 const& t) { std::cout << "Called with argument of type Type2\n"; t.print(); }
);
std::visit(action, t_);
}
};
where we need a little boilerplate for the visitation to work
template<typename... Ts>
struct combined : Ts... {
combined(Ts... ts)
: Ts(ts)... {
}
using Ts::operator()...;
};
template <typename... Ts>
auto make_combined(Ts&&... ts) {
return combined<std::decay_t<Ts>...>{std::forward<Ts>(ts)...};
}
The calling code then looks like this:
int main() {
Type t1 = Type1{};
auto a1 = Action{t1};
a1.print();
auto a2 = Action{Type2{}};
a2.print();
}
The working example can be found on wandbox.
If you later would like to add an additional type Type3
, just create it, add it to the variant in the Type
using-declaration and the compiler will tell you where you need to add a new handler for the new type.
If you prefer some kind of default behavior for types not explicitly mentioned in the Action
class, you can also just add a generic lambda to the combined lambda in the Action
class like:
auto action = make_combined(
[](Type1 const& t) { std::cout << "Called with argument of type Type1\n"; t.print(); },
[](Type2 const& t) { std::cout << "Called with argument of type Type2\n"; t.print(); },
[](auto const& t) { std::cout << t.print(); } // this will be selected for all other types
);
Upvotes: 1
Reputation: 2568
Your code is working and of good design. As said in the comments "you put the functionality in Type1
/Type2
and use polymorphism to differentiate the implementation" which is a good approach.
You can follow this approach and do the implementation in Type1
/Type2
and control the ouput in Action
this way.
Dynamic cast
Regardless point one you can achieve what you want by just checking the types. You want to check if t
in the constructor of Action
is of type Type1
or of type Type2
. Simply try to downcast it using dynamic_cast<T>
.
Your code for Action::print()
could look similar to this:
Action::print(){
Type1* t1 = dynamic_cast<Type1*>(t); /* returns nullptr if t is not of type Type1
/* Check if we got the nullptr or not */
if(t1){
std::cout << "I am an action. My type says: Type 1!" << std::endl;
}
else{
std::cout << "I am an action. My type says: Type 2!" << std::endl;
}
}
Let's check in int main()
:
int main(){
Type *t1 = new Type1();
Action *act1 = new Action(t1);
act1->print();
Type *t2 = new Type2();
Action *act2 = new Action(t2);
act2->print();
delete act1,act2,t1,t2;
}
Output:
I am an action. My type says: Type 1!
I am an action. My type says: Type 2!
Please not that this is a minimal example and that I think a solution using clear structured polymorphism is the better alternative.
Upvotes: 1