Dynamically build a function

I am afraid that something will be answered on this site, but I can’t find it because I don’t even know how to formulate the question. So here is the problem:

I have a voxel function. First I calculate the offsets, angles and stuff, and after I doze. But I make several versions of each function, because sometimes I want to copy a pixel, sometimes blit, sometimes blit 3 * 3 squares for each pixel for a smoothing effect, and sometimes just copy a pixel to n * n pixels on the screen if the object changes. And there are tons of versions for this small part in the center of the function.

What can I do instead of writing 10 identical functions that differ only in the central part of the code? For performance reasons, passing a function pointer as an argument is not an option. I'm not sure if doing them inline will do the trick, because the arguments I send are different: sometimes I calculate the volume (Z value), sometimes I know that the pixels are drawn from the bottom up.

I guess there is some way to do this in C ++ that everyone knows about. Please tell me what I need to learn this. Thanks.

+4
source share
5 answers

OO's traditional approaches to this are the method template template and the strategy template.

Template method

Firstly, it is an extension of the method described in Vincenzo’s answer: instead of writing a simple non-virtual shell, you are writing a non-virtual function containing the entire algorithm. Those parts that may vary are calls to virtual functions. The specific arguments needed for this implementation are stored in the object of the derived class that provides this implementation.

eg.

class VoxelDrawer { protected: virtual void copy(Coord from, Coord to) = 0; // any other functions you might want to change public: virtual ~VoxelDrawer() {} void draw(arg) { for (;;) { // implement full algorithm copy(a,b); } } }; class SmoothedVoxelDrawer: public VoxelDrawer { int radius; // algorithm-specific argument void copy(Coord from, Coord to) { blit(from.dx(-radius).dy(-radius), to.dx(-radius).dy(-radius), 2*radius, 2*radius); } public: SmoothedVoxelDrawer(int r) : radius(r) {} }; 

Strategy

This is similar, but instead of using inheritance, you pass the Copier polymorphic object as an argument to your function. It is more flexible as it separates your various copy strategies from a specific function, and you can reuse your copy strategies in other functions.

 struct VoxelCopier { virtual void operator()(Coord from, Coord to) = 0; }; struct SmoothedVoxelCopier: public VoxelCopier { // etc. as for SmoothedVoxelDrawer }; void draw_voxels(arguments, VoxelCopier &copy) { for (;;) { // implement full algorithm copy(a,b); } } 

Although more subtle than passing a pointer to a function, neither a template method nor a strategy will likely have better performance than just passing a pointer to a function: run-time polymorphism is still an indirect function call.

Politics

The modern equivalent of a C ++ strategy strategy template is a policy template. This simply replaces the polymorphism at runtime with a compile-time polymorphism to avoid invoking an indirect function and enable nesting

 // you don't need a common base class for policies, // since templates use duck typing struct SmoothedVoxelCopier { int radius; void copy(Coord from, Coord to) { ... } }; template <typename CopyPolicy> void draw_voxels(arguments, CopyPolicy cp) { for (;;) { // implement full algorithm cp.copy(a,b); } } 

Due to type inference, you can just call

 draw_voxels(arguments, SmoothedVoxelCopier(radius)); draw_voxels(arguments, OtherVoxelCopier(whatever)); 

NB. I was a bit incompatible here: I used operator() to make my strategy call look like a regular function, but a regular method for my policy. As long as you pick one and stick to it, it's just a matter of taste.

CRTP Template Method

There is one last mechanism, which is a version of the compile-time polymorphism method of the template, and uses the Curiously Recurring Template.

 template <typename Impl> class VoxelDrawerBase { protected: Impl& impl() { return *static_cast<Impl*>(this); } void copy(Coord from, Coord to) {...} // *optional* default implementation, is *not* virtual public: void draw(arg) { for (;;) { // implement full algorithm impl().copy(a,b); } } }; class SmoothedVoxelDrawer: public VoxelDrawerBase<SmoothedVoxelDrawer> { int radius; // algorithm-specific argument void copy(Coord from, Coord to) { blit(from.dx(-radius).dy(-radius), to.dx(-radius).dy(-radius), 2*radius, 2*radius); } public: SmoothedVoxelDrawer(int r) : radius(r) {} }; 

Summary

In general, I would prefer a strategy / policy templates for their lower connection and better reuse, and select the template method template only where the top-level algorithm that you parameterize is really set in stone (i.e., either refactoring existing code, either really confident in your analysis of the points of variation), and reuse is really not a problem.

It is also very painful to use the template method if there is more than one axis of variation (i.e. you have several methods, such as copy , and you want to change their implementation independently). You either get code duplication or mixin inheritance.

+7
source

I suggest using NVI idioms.

You have your own public method that calls a private function that implements logic, which should be different from case to case.

Derived classes will need to provide the implementation of this private function, which specializes in their specific task.

Example:

 class A { public: void do_base() { // [pre] specialized_do(); // [post] } private: virtual void specialized_do() = 0; }; class B : public A { private: void specialized_do() { // [implementation] } }; 

The advantage is that you can save the general implementation in the base class and drill down to it as required for any subclass (for which you just need to override the specialized_do method).

The disadvantage is that for each implementation you need a different type, but if your use case uses different user interface elements, this is the way to go.

+3
source

You can just use the strategic template.

So, instead of something like

 void do_something_one_way(...) { //blah //blah //blah one_way(); //blah //blah } void do_something_another_way(...) { //blah //blah //blah another_way(); //blah //blah } 

You will have

 void do_something(...) { //blah //blah //blah any_which_way(); //blah //blah } 

any_which_way can be a lambda, a functor, a virtual member function of a strategy class passed to. There are many options.

You are sure that

"passing a function pointer as an argument is not an option"

Is it really slowing down?

+1
source

You can use functions of a higher order if your "central part" can be parameterized. Here is a simple example of a function that returns a function that adds n to its argument:

 #include <iostream> #include<functional> std::function<int(int)> n_adder(int n) { return [=](int x){return x+n;}; } int main() { auto add_one = n_adder(1); std::cout<<add_one(5); } 
0
source

You can use a template template template or strategy template . Typically, the template template template is used in white blocks when you need to know about the internal structure of the framework to properly subclass the class. A strategy template is usually used in black boxes when you do not need to know about the implementation of the framework, since you only need to understand the contract of the methods that you must implement.

For performance reasons, passing a function pointer as an argument is not an option.

Are you sure that you pass one additional parameter and will cause performance problems? In this case, you may have similar performance penalties if you use OOP methods such as the template method or strategy. But it is usually necessary to use a profiler to determine what is the source of performance degradation. Virtual calls, passing additional parameters, calling a function through a pointer, as a rule, are very cheap compared to complex algorithms. You may find that these methods consume a small percentage of processor resources compared to other code.

I'm not sure that making them inline will do the trick, because the arguments I send are different: sometimes I calculate the volume (Z value), sometimes I know that the pixels are drawn from the bottom up.

You can pass all the parameters necessary for drawing in all cases. Alternatively, if you use the Tempate method template, the base class can provide methods that can return data that may be needed for painting in different cases. In the strategy template, you can pass an instance of an object that can provide this type of data for implementing the strategy.

0
source

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


All Articles