我有两个派生自同一基类的类。在编译时,可以知道哪一个是基于宏定义创建的。我有另一个用户类,并调用成员函数(每个类不同)。它看起来像这样:
class User() {
void useClass( Base* p ) {
#ifdef useA
p->aFun();
#else
p->bFun()
#endif
}
class Base() {}
class A : public Base {
void aFun();
}
class B : public Base {
void bFun();
}
class C {
C() {
#ifdef useA
p = new A();
#else
p = new B();
#endif
}
Base* p;
User m_user;
void doStuffWithUser() {
user.useClass( p );
}
}
我想减少宏的数量,所以我想知道是否有更好的方法来做到这一点。特别是,User类中的#ifdef对我来说看起来不太好。有没有办法在不使用宏的情况下重现它?理想情况下,没有运行时检查来确定p是什么类型。
编辑: 这两个派生类有不同的成员需要调用,尽管有继承,但这个不能改变。
答案 0 :(得分:1)
解决方案是visitor pattern。
我们的想法是拥有两个类:访客和访问。
visitor 用于根据对象的实际类型调用函数。 访问是您的类层次结构的对象。
在您的示例中,您可以执行以下操作:
class User() {
void useClass( Base* p ) {
p->visit(visitor);
}
class Base() {
virtual void visit(Visitor) = 0;
}
class A : public Base {
void aFun();
virtual void visit(Visitor v) override {
v.visit(this);
}
}
class B : public Base {
void bFun();
virtual void visit(Visitor v) override {
v.visit(this);
}
}
class Visitor {
void visit(B* b) {
b->bFun();
}
void visit(A* a) {
a->aFun();
}
}
通过使用visit
函数进行双重调度,可以确保根据实际类型调用函数。
我认为你的问题没有编译时解决方案,因为在useClass
(现在是),没有办法(在编译时)知道{{1}的实际类型}。如果您想要编译时解决方案,则需要进行更多更改。例如,将p
设为模板或将其重载,这意味着您无法再使用useClass
致电useClass
...
答案 1 :(得分:1)
A和B共享一个公共基类的事实是无关紧要的,因为它们具有您正在使用的不同接口。
我会使C成为模板并存储指向派生类而不是基类的指针:
Stream
然后你可以简单地重载useClass()来接受A或B:
template<typename T>
class CT {
public:
CT() {
p = std::make_unique<T>();
}
std::unique_ptr<T> p;
User m_user;
void doStuffWithUser() {
user.useClass(p);
}
};
现在你只有一个编译时间开关:
class User {
public:
void useClass(A* p) {
p->aFun();
}
void useClass(B* p) {
p->bFun();
}
};
答案 2 :(得分:0)
您可以将aFun和bFun重命名为Fun并将其设置为虚拟(也将其添加到Base类中)并在useClass中使用Fun方法,编译器将确定使用哪种方法。 这将消除第一个宏。
对于第二个也许你应该用其他方式重写它,所以你根本不会使用宏。我不认为你没有宏可以重现这种行为。 也许您应该为构造函数提供一些标志,1创建对象A或0以创建对象B并在运行时从用户获取此标志。
EDIT 所以也许你可以创建函数Fun,在A类中调用aFun,在B类中调用bFun。
答案 3 :(得分:0)
您可以为User
类创建模板,并将其专门用于A类和B类:
template<typename T>
class User
{
void useClass( Base* p );
}
template<>
class User<A>
{
void useClass( Base* p ) {p->aFun();}
};
template<>
class User<B>
{
void useClass( Base* p ) {p->bFun();}
};
现在在C班:
template<typename T>
class C {
C() {
p = new T();
}
Base* p;
User<T> m_user;
void doStuffWithUser() {
m_user.useClass( p );
}
}
作为最后一点,请避免使用新的运算符。尝试std :: unique_ptr或std :: shared_prt
PS。我还没有测试过这段代码
答案 4 :(得分:0)
如果您不想更改任何界面,可以使用单个#ifdef
class Base {};
class A : public Base {
public:
void aFun(){}
};
class B : public Base {
public:
void bFun(){}
};
#ifdef useA
typedef A impl_type;
auto correct_func = &impl_type::aFun;
#else
typedef B impl_type;
auto correct_func = &impl_type::bFun;
#endif
class User {
public:
void useClass( Base* p ) {
auto pointer = (static_cast<impl_type*>(p));
(pointer->*correct_func)();
}
};
class C {
C() {
p = new impl_type();
}
Base* p;
User m_user;
void doStuffWithUser() {
m_user.useClass( p );
}
};
答案 5 :(得分:-1)
可能你可以在A和B中命名两个具有相同名称的函数并将其设置为虚拟,因此useClass将只调用所需的函数。
喜欢
class User() {
void useClass( Base* p ) {
p->fun();
}
};
class Base() {
virtual void fun() = 0;
};
class A : public Base {
void fun();
};
class B : public Base {
void fun();
};
此外,您可以使用某种constexpr函数(如果您使用的是c ++ 11标准或更新版本)来确定p是什么类型。
修改强> 看到评论后,我认为你可能会离开你的aFun(),bFun(),只需添加一些fun()函数,它将被派生并调用特定于类型的函数。 此外,尝试创建一些具有相同接口的适配器类(如gof模式)可能会有所帮助。
Edit2:我的意思是可能会有像
这样的功能constexpr Base* chooseType(int a){
if(a == 0){
return new A();
} else {
return new B();
}
}
/////
C() {
int case = 0;
p = chooseType(case);
}
它将在编译时调用,以便选择类。
答案 6 :(得分:-1)
如果您无法更改界面,想要摆脱#ifdef
,并且对所使用的类型进行编译时保证,而不进行运行时检查 - 我建议使用{的组合{1}}和重载函数。
首先,我会将班级template
更改为C
:
template
然后,会更改template<typename Type>
class C
{
static_assert(std::is_base_of<Base, Type>::value, "Template argument of C is not base of Base!");
public:
C () {p = new Type;}
~C() {delete p;}
void fun () {u.useClass (p);}
private:
Type* p;
User u;
};
类,以便在具有重载函数的User
的不同可能实现之间切换:
Base
然后,您将创建class User
{
public:
void useClass (A* p) {p->aFun();}
void useClass (B* p) {p->bFun();}
};
的对象,如下所示:
C
如果您忘记实现特定于类型的C<A> ca;
,或者尝试在useClass
中使用错误的类型(即不从C
继承),则会出现编译时错误。< / p>
此外,如果要在其间切换的某些子类Base
具有非默认构造函数,则可以将仿函数(例如Base
)传递给{{1构造函数,并使用它来创建一个对象。
这样的构造函数可能看起来像:
std::function<Type*()>
使用它看起来像:
C