Avoid RTTI when dealing with pairs of objects

倖福魔咒の 提交于 2020-01-06 02:27:04

问题


I saw a few questions about avoiding RTTI, but mine seems to be a bit more specific. Here is an example case:

struct Base {};

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

std::vector<Base*> vec;

I want to do something on all possible (unordered) pairs of objects in the vector (if the vector has 3 elements, with 0 and 1, 0 and 2, 1 and 2). The pseudo-code for what I want is something like:

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

Lots of people say that RTTI is bad design, but could it be avoided here? And is there a more efficient way than doing all these if/elif?


回答1:


Whether you choose to use or avoid RTTI is really more about common sense. While it may be considered good design to strive to avoid it, occasionally you just want to get something done and move on.

If you only have a couple of class types to deal with, you could get rid of the if/else if and have a simple table of function pointers. Use a virtual function in each type to add weight to the index used to look up the correct function to call:

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 );



回答2:


This is a prime usecase for binary variant visitation.

This shifts runtime polymorphism somewhat to static polymorphism (though a type-discriminant is still being used inside boost::variant, but this does not use or require RTTI).

Note also, how you don't absolutely need to add separate cases for all combinations: I've demonstrated using template behaviour implementations for the case where

  • arguments are of the same actual type ("identical")
  • no other overload existed

I have also shown how you can still mix "classical" runtime-polymorphism by showing the overload that takes Base, A (accepting A,B,C,... in combination with a second argument of type A (or derived).

Finally, note that this approach allows you to overload on rvalue-ness, const-ness, volatility as well.

#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";
}

See it Live on Coliru, output:

A, B
A, C
identical
identical
identical
[ABC], A
Other



回答3:


Add the following to base:

virtual void behaviour(Context& context) = 0;

The implement for all derived classes.

If you can make the context the same for all calls to behaviour you should be able to eliminate any need to RTTI checks. Each implementation can use whatever it needs from context.




回答4:


Make behaviour() call virtual functions in your A, B, C objects to perform the specific work.

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);
}



回答5:


I think you can use a cache of map:

map<string, func*>;

func* is functional pointer which can point to a function behavior(A,B) or behavior(A,C) or behavior(B, C)

when you create a object of A and B insert(make_pair(AB, behavior(A,B)) and the same for B and C, when you want use a A and B objects you can fetch from the map,and the same for B and C.



来源:https://stackoverflow.com/questions/18013217/avoid-rtti-when-dealing-with-pairs-of-objects

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!