,...">

C ++ dual send "extensible" without RTTI

Does anyone know a way to correctly handle double dispatch in C ++ without using RTTI and dynamic_cast <>, as well as a solution in which the class hierarchy is extensible, that is, the base class can be obtained from what follows and its definition / Implementation should not be aware of this ?
I suspect there is no way, but I would be happy to be mistaken :)

+6
source share
5 answers

The first thing to understand is that double (or higher order) sending does not scale. With one dispatch and n , you need functions n ; for double sending n^2 and so on. How do you this problem partially determines how you handle double submission. One obvious solution is to limit the number of derived types by creating a closed hierarchy; in this case, dual sending can be easily implemented using the visitor template option. If you do not close the hierarchy, then you have several possible approaches.

If you insist that each pair corresponds to a function, then you basically need:

 std::map<std::pair<std::type_index, std::type_index>, void (*)(Base const& lhs, Base const& rhs)> dispatchMap; 

(If necessary, adjust the signature of the function). You also need to implement the n^2 functions and paste them into the dispatchMap . (I assume that you are using free functions, there is no logical reason to put them in one of the classes and not the other.) After that, you call:

 (*dispatchMap[std::make_pair( std::type_index( typeid( obj1 ) ), std::type_index( typeid( obj2 ) )])( obj1, obj2 ); 

(You obviously want to wrap this in a function, this is not the thing you want to scatter throughout the code.)

A smaller option would be to say that only certain combinations are legal. In this case, you can use find on dispatchMap and generate an error if you do not find what you are looking for. (Expect a lot of mistakes.) The same solution can be used if you can define some type of default behavior.

If you want to do this 100% correctly, some of the functions that can handle the intermediate class and all its derivatives, then you will need some more dynamic search and order control overload. Consider, for example:

  Base / \ / \ I1 I2 / \ / \ / \ / \ D1a D1b D2a D2b 

If you have f(I1, D2a) and f(D1a, I2) you need to choose. The simplest solution is simply a linear search, selecting the first one that you can name (as defined by dynamic_cast on pointers to objects) and manually controlling the insertion order to determine the overload you want. However, with n^2 functions, this can slow down pretty quickly. since there is order, you can use std::map , but the ordering function will be categorically non-trivial to implement (and still have to use dynamic_cast all over the place).

All things considered, my suggestion would be to limit double sending to small, closed hierarchies, and stick to some version of the visitor template.

+6
source

The "visitor pattern" in C ++ often equates to double submission. It does not use RTTI or dynamic_casts.

See also the answers to this question .

+6
source

The first problem is trivial. dynamic_cast includes two things: runtime checking and type listing. The first requires RTTI, and the second does not. All you have to do to replace dynamic_cast with functionality that does the same thing without requiring RTTI is to have your own method for checking type at runtime. To do this, you need a simple virtual function that returns some identification of what type it is or which more specific interface corresponds to it (it can be an enumeration, an integer identifier, even a string). For translation, you can safely execute static_cast as soon as you have already checked the runtime, and make sure that the type you are using is in the hierarchy of objects. So, this solves the problem of emulating the "full" functionality of dynamic_cast without the need to use the built-in RTTI. Another, more active solution is to create your own RTTI system (as is done in several software tools, such as LLVM, which Matthew mentioned).

The second problem is a big one. How to create a dual submit mechanism that scales well with an extensible class hierarchy. It's hard. At compile time (static polymorphism), this can be done pretty well with function overloads (and / or specialized patterns). During operation, it is much more complicated. As far as I know, the only solution, as Conrad mentioned, is to save the function pointer dispatch table (or something similar). With some use of static polymorphism and splitting of send functions into categories (for example, function signatures, etc.), you can avoid a type of security violation, in my opinion. But before you implement this, you should seriously think about your design to make sure that this double dispatch is really necessary if it really needs to be a run-time dispatcher, and if it really needs to have a separate function for each combination of two classes (maybe you can create a reduced and fixed number of abstract classes that capture all the really different methods that you need to implement).

+3
source

You can check how LLVM implements isa<> , dyn_cast<> and cast<> as a template system since it is compiled without RTTI.

It's a bit cumbersome (requires a tidbit of code in each class), but very lightweight.

LLVM Programmer Manual has a nice example and a reference to the implementation.

(All three methods use the same piece of code)

+2
source

You can fake the behavior by executing compilation logic during multiple submission yourself. However, this is very tiring. Bjarne Straustrup is a co-author of an article describing how this can be implemented in the compiler.

The main mechanism - the dispatch table - can be dynamically generated. However, using this approach, you will of course lose all syntactic support. You need to maintain a 2-dimensional matrix of method pointers and manually search for the correct method depending on the types of arguments. This would provide a simple (hypothetical) challenge.

 collision(foo, bar); 

at least as complicated as

 DynamicDispatchTable::lookup(collision_signature, FooClass, BarClass)(foo, bar); 

since you did not want to use RTTI. And this assumes that all of your methods accept only two arguments. As soon as additional arguments are required (even if they are an integral part of multiple submissions), it becomes more complex and requires a type safety bypass.

+2
source

Source: https://habr.com/ru/post/890507/


All Articles