C ++编译时类型确定

时间:2017-05-26 10:36:27

标签: c++

我有两个派生自同一基类的类。在编译时,可以知道哪一个是基于宏定义创建的。我有另一个用户类,并调用成员函数(每个类不同)。它看起来像这样:

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是什么类型。

编辑: 这两个派生类有不同的成员需要调用,尽管有继承,但这个不能改变。

7 个答案:

答案 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