我有一个名为Type的虚拟类,以及一个派生类Type1和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; }
};
根据用户输入的参数,我将实例化一个类或另一个类。
然后我希望有另一个会产生动作的类,具体取决于作为参数给出的“Type”对象的类型。目前,这门课程是:
class Action {
protected:
Type *t;
public:
Action(Type *t) : t(t) {} ;
void print() {
cout << "I am an action. My type says: ";
t->print();
}
};
如何让Action :: print根据属性“t”的类型生成不同的任务?
我尝试了什么:
我的愿望是拥有一个看起来像这样的主体:
int main(int argc, const char * argv[]) {
Type *t = new Type1();
Action *act = new Action(t);
act->print();
delete act, t;
return 0;
}
但是根据t的类型调用不同的打印方法。这是可能的(以及如何)?
修改
阅读完评论后,有两种主要设计可供选择:
创建一个类Action,并调用特定方法(print(Type1 *)或print(Type2 *))。我认为这是评论中建议的访客模式。
创建两个类,Action1和Action2(可能派生自Action,或不派生!),并实例化与t,Type1或Type2对应的类。
一种解决方案比另一种解决方案更清洁,更易于维护吗?
修改2
一些评论中提到的工厂模式怎么样?任何人都可以告诉我它解决或不解决我的问题?
答案 0 :(得分:2)
首先是一个小小的评论:我个人认为'使用命名空间'是一种不好的做法,因为它会通过头文件涟漪。原因在整个互联网上描述。
此外,由于异常安全,我总是尝试最小化使用指针。 Sutter有一本很好的书,名为Exceptional C ++,它详细描述了这些问题。但是,指针显然有其用途,特别是多态性。最后,如果他们只有公共成员,我喜欢制作类结构......这只是一个品味问题。
让我们从一些代码开始:
#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;
}
您似乎要解决的第一个问题是用户输入生成类型的事实。根据输入,这可能指向抽象工厂或构建器模式,甚至是完整的解析器,但如果它是一个简单的输入决策,最好是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;
}
通常,您希望将模型与实现分开。动作有许多不同的形状和形式,因此您可能希望根据您的模型做其他事情。一个访客模式,双重调度,或任何你想称之为它的东西来到这里救援。
请注意,我通常使用一个稍微修改过的访问者,它会将基本类型作为默认值返回。这样,您可以轻松地将转换合并到您的代码中,这是一个常见的设计要求。此时我只想使用指针。就个人而言,我是内存领域的忠实粉丝,可以解决内存管理问题,但我认为这超出了这个答案的范围。
为了完成这项任务,一个简单的基类可以帮助你摆脱无意义的管道。
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;
}
答案 1 :(得分:1)
您的代码工作正常且设计良好。正如评论中所述,您将功能放在progCode
/ Type1
中并使用多态来区分实现“,这是一种很好的方法。
您可以按照此方法执行Type2
/ Type1
中的实施,并以Type2
方式控制输出。
动态广告
无论如何,您只需检查类型即可实现您想要的效果。您想检查Action
的构造函数中的t
是Action
类型还是Type1
类型。只需尝试使用Type2
向下转发它。
dynamic_cast<T>
的代码可能与此类似:
Action::print()
让我们检查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;
}
}
:
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;
}
请注意,这不是一个最小的例子,我认为使用清晰结构化多态的解决方案是更好的选择。
答案 2 :(得分:1)
虽然基于继承的多态性确实有其用途,但这听起来并不像其中之一。通常,在基于继承的设计中,执行的任务应该仅取决于所涉及的类的行为,而不是取决于它们的具体类型。
围绕该继承限制的一种方法是使用Visitor模式,它基本上通过它对访问请求的反应来公开继承层次结构中类的具体类型。
您可以通过访问者模式解决基于继承的设计的一个用例是当您无法控制其代码库的用户可能添加其他派生类时。但据我所知,这不是一个问题。
所以让其工作的另一种方法是通过variant
而不是继承(我在这里使用c ++ 17 std::variant
,但你可以对Boost做同样的事情。如果您无法访问已实现std::variant
的编译器/ STL,则为Variant。)只需将类型定义为
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";
}
};
并定义&#34;基本类型&#34;使用using
- 声明
using Type = std::variant<Type1, Type2>;
行动本身看起来像这样:
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_);
}
};
我们需要一些样板来进行访问
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)...};
}
调用代码如下所示:
int main() {
Type t1 = Type1{};
auto a1 = Action{t1};
a1.print();
auto a2 = Action{Type2{}};
a2.print();
}
可以在wandbox上找到工作示例。
如果您以后想要添加其他类型Type3
,只需创建它,将其添加到Type
using-declaration中的变体中,编译器将告诉您需要添加的位置新类型的新处理程序。
如果您更喜欢Action
类中未明确提到的类型的某种默认行为,您还可以将通用lambda添加到Action
类中的组合lambda中,如:
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
);
答案 3 :(得分:1)
使用混音,您可以非常自然地编写课程。您只需扩展CRTP模式,以便以后能够使用mixin技术。代码比任何文本解释都简单,所以请自己看看:
#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();
}
答案 4 :(得分:0)
这就是我的目的。
#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;
}
输出结果为:
I am an action1. My type says: I am of type 1
I am an action2. My type says: I am of type 2
似乎它完成了预期的工作。如果有人发现错误,请告诉我。