1

我看到了一些关于避免 RTTI 的问题,但我的问题似乎更具体一些。这是一个示例案例:

struct Base {};

struct A : Base {};
struct B : Base {};
struct C : Base {};

std::vector<Base*> vec;

我想对向量中所有可能的(无序的)对象对做一些事情(如果向量有 3 个元素,分别为 0 和 1、0 和 2、1 和 2)。我想要的伪代码类似于:

if e1 is A and e2 is A:
    behavior1(e1, e2)
elif e1 is A and e2 is B:
    behavior2(e1, e2)
elif ...

很多人说 RTTI 是糟糕的设计,但这里可以避免吗?还有比做所有这些 if/elif 更有效的方法吗?

4

5 回答 5

2

无论您选择使用还是避免使用 RTTI,实际上更多的是关于常识。虽然努力避免它可能被认为是好的设计,但有时你只是想完成一些事情并继续前进。

如果你只有几个类类型要处理,你可以去掉if/else if并有一个简单的函数指针表。在每种类型中使用一个虚函数来为用于查找要调用的正确函数的索引添加权重:

struct Base
{
    virtual int  GetWeight() const = 0;
};

struct A : Base
{
    virtual int  GetWeight() const    { return 1; }
};

struct B : Base
{
    virtual int  GetWeight() const    { return 2; }
};

struct C : Base
{
    virtual int  GetWeight() const    { return 4; }
};


static void (*MyBehaviours)( Base*, Base* )[] = { &behaviour1, &behaviour2, ... };

MyBehaviours[ e1->GetWeight() + e2->GetWeight() ]( e1, e2 );
于 2013-08-02T09:36:19.410 回答
2

这是二进制变体访问的主要用例。

这在一定程度上将运行时多态性转变为静态多态性(尽管内部仍在使用类型判别boost::variant,但这不使用或不需要 RTTI)。

另请注意,您绝对不需要为所有组合添加单独的案例:我已经演示了使用模板行为实现的案例

  • 参数具有相同的实际类型(“相同”)
  • 不存在其他过载

我还展示了如何通过显示所采用的重载(接受 A、B、C、... 并结合 A 类型(或派生)的第二个参数来混合“经典”运行时多态性。Base, A

最后,请注意,这种方法还允许您重载右值、常量和波动性。

#include <iostream>
#include <boost/variant.hpp>
#include <string>

struct Base {};

struct A: Base {};
struct B: Base {};
struct C: Base {};

typedef boost::variant<A, B, C> MyType;

struct MyBehaviour : boost::static_visitor<std::string>
{
    template <typename T>
    std::string operator()(T const&, T const&) const { return "identical"; }

    std::string operator()(A const&, B const&) const { return "A, B"; }
    std::string operator()(A const&, C const&) const { return "A, C"; }

    std::string operator()(Base const&, A const&) const { return "[ABC], A"; }

    std::string operator()(Base const&, Base const&) const { return "Other"; }
};

int main()
{
    MyBehaviour f;

    MyType a = A{},
           b = B{},
           c = C{};

    std::cout << boost::apply_visitor(f, a, b) << "\n";
    std::cout << boost::apply_visitor(f, a, c) << "\n";

    std::cout << boost::apply_visitor(f, a, a) << "\n";
    std::cout << boost::apply_visitor(f, b, b) << "\n";
    std::cout << boost::apply_visitor(f, c, c) << "\n";

    std::cout << boost::apply_visitor(f, c, a) << "\n";

    std::cout << boost::apply_visitor(f, c, b) << "\n";
}

在 Coliru 上看到它,输出:

A, B
A, C
identical
identical
identical
[ABC], A
Other
于 2013-08-02T09:52:19.210 回答
0

将以下内容添加到基础:

virtual void behaviour(Context& context) = 0;

所有派生类的实现。

如果您可以context对所有行为调用进行相同的处理,那么您应该能够消除对 RTTI 检查的任何需要。每个实现都可以使用它需要的任何东西context

于 2013-08-02T09:34:18.950 回答
0

让 behavior() 调用 A、B、C 对象中的虚函数来执行特定工作。

struct Base 
{
    virtual doSomething(){};
};

struct A : Base 
{
    virtual doSomething(){  };
};
struct B : Base 
{
    virtual doSomething(){};
};

std::vector<Base*> vec;

void performOperation(Base* a, Base* b)
{
   a->doSomething(a, b);
   b->doSomething(a, b);
}

int myFunction
{
    // ... code to select a pair of objects omitted
    performOperation(a, b);
}
于 2013-08-02T09:35:38.737 回答
0

我认为您可以使用地图缓存:

map<string, func*>;

func*是函数指针,它可以指向一个函数behavior(A,B)behavior(A,C)behavior(B, C)

当您创建 A 和 B 的对象时,insert(make_pair(AB, behavior(A,B))对于B and C,当您想要使用 A 和 B 对象时,您可以从地图中获取,对于 B 和 C 也是如此。

于 2013-08-02T09:33:39.413 回答