TranslateProject/sources/tech/20210222 A friendly guide to the syntax of C-- method pointers.md
Xingyu Wang b2fe7a96ea 超期回收
@chensanle @amwps290 @MZqk @mengxinayan @scvoet @Tracygcz @MjSeven @RiaXu
请注意,申请文件已经超期回收。如果您还在此篇文章上工作,请重新申请并完成。
2021-04-19 08:54:23 +08:00

8.6 KiB

A friendly guide to the syntax of C++ method pointers

Once you understand the general principles, C++ method pointers become less intimidating. Person drinking a hot drink at the computer

If you're looking for performance, complexity, or many possible solutions to solve a problem, C ++ is always a good candidate when it comes to extremes. Of course, functionality usually comes with complexity, but some C++ peculiarities are almost illegible. From my point of view, C++ method pointers may be the most complex expressions I've ever come across, but I'll start with something simpler.

The examples in this article are available in my GitHub repository.

C: Pointer to functions

Let's begin with some basics: Assume you have a function that takes two integers as arguments and returns an integer:

int sum(int a, intb){
    return a+b;
}

In plain C, you can create a pointer to this function, assign it to your sum(...) function, and call it by dereferencing. The function's signature (arguments, return type) must comply with the pointer's signature. Aside from that, a function pointer behaves like an ordinary pointer:

int (*funcPtrOne)(int, int);

funcPtrOne = ∑

int resultOne = funcPtrOne(2, 5);

It gets a bit uglier if you take a pointer as an argument and return a pointer:

int *next(int *arrayOfInt){
    return ++arrayOfInt;
}

int *(*funcPtrTwo)(int *intPtr);

funcPtrTwo = &next;

int resultTwo = *funcPtrTwo(&array[0]);

Function pointers in C store the address of a subroutine.

Pointers to methods

Let's step into C++: The good news is that you probably won't need to use pointers to methods, except in a few rare cases, like the following one. First, define a class with member functions you already know:

class MyClass
{
public:

    int sum(int a, int b) {
        return a+b;
    }

};

1. Define a pointer to a method of a certain class type

Declare a pointer to a method of the MyClass type. At this point, you don't know the exact method you want to call. You've only declared a pointer to some arbitrary MyClass method. Of course, the signature (arguments, return type) matches the sum(…) method you want to call later:

`int (MyClass::*methodPtrOne)(int, int);`

2. Assign a certain method

In contrast to C (or static member functions), method pointers don't point to absolute addresses. Each class type in C++ has a virtual method table (vtable) that stores the address offset for each method. A method pointer refers to a certain entry in the vtable, so it also stores only the offset value. This principle also enables dynamic dispatch.

Because the signature of the sum(…) method matches your pointer's declaration, you can assign the signature to it:

`methodPtrOne = &MyClass::sum;`

3. Invoke the method

If you want to invoke the method with the pointer, you have to provide an instance of the class type:

MyClass clsInstance;
int result = (clsInstance.*methodPtrOne)(2,3);

You can access the instance with the . operator, dereference the pointer with a *, and thus call the method by providing two integers as arguments. Ugly, right? But you can still go a step further.

Using method pointers within a class

Assume you are creating an application with a client/server principle architecture with a backend and a frontend. You don't care about the backend for now; instead, you will focus on the frontend, which is based on a C++ class. The frontend's complete initialization relies on data provided by the backend, so you need an additional initialization mechanism. Also, you want to implement this mechanism generically so that you can extend your frontend with other initialization methods in the future (maybe dynamically).

First, define a data type that can store a method pointer to an initialization method (init) and the information describing when this method should be called (ticks):

template<typename T>
struct DynamicInitCommand {
    void (T::*init)();     // Pointer to additional initialization method
    unsigned int ticks;    // Number of ticks after init() is called
};

Here is what the Frontend class looks like:

class  Frontend
{
public:

    Frontend(){
        DynamicInitCommand<Frontend> init1, init2, init3;

        init1 = { &Frontend::dynamicInit1, 5};
        init2 = { &Frontend::dynamicInit2, 10};
        init3 = { &Frontend::dynamicInit3, 15};

        m_dynamicInit.push_back(init1);
        m_dynamicInit.push_back(init2);
        m_dynamicInit.push_back(init3);
    }
   
   

    void  tick(){
        std::cout << "tick: " << ++m_ticks << std::endl;
       
        /* Check for delayed initializations */
        std::vector<DynamicInitCommand<Frontend>>::iterator  it = m_dynamicInit.begin();

        while (it != m_dynamicInit.end()){
            if (it->ticks < m_ticks){
                 
                if(it->init)
                    ((*this).*(it->init))(); // here it is

                it = m_dynamicInit.erase(it);

            } else {
                it++;
            }
        }
    }
   
    unsigned  int  m_ticks{0};
   
private:

    void  dynamicInit1(){
        std::cout << "dynamicInit1 called" << std::endl;
    };

    void  dynamicInit2(){
        std::cout << "dynamicInit2 called" << std::endl;
    }

    void  dynamicInit3(){
        std::cout << "dynamicInit3 called" << std::endl;
    }

    unsigned  int  m_initCnt{0};
    std::vector<DynamicInitCommand<Frontend> > m_dynamicInit;
};

After Frontend is instantiated, the tick() method is called at fixed intervals by the backend. For example, you can call it every 200ms:

int  main(int  argc, char*  argv[]){
    Frontend frontendInstance;

    while(true){
        frontendInstance.tick(); // just for simulation purpose
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
}

Frontend has three additional initialization methods that must be called based on the value of m_ticks. The information about which initialization method to call at which tick is stored in the vector m_dynamicInit. In the constructor (Frontend()), append this information to the vector so that the additional initialization functions are called after five, 10, and 15 ticks. When the backend calls the tick() method, the value m_ticks is incremented, and you iterate over the vector m_dynamicInit to check whether an initialization method has to be called.

If this is the case, the method pointer must be dereferenced by referring to this:

`((*this).*(it->init))()`

Summary

Methods pointers can get a bit complicated if you're not familiar with them. I did a lot of trial and error, and it took time to find the correct syntax. However, once you understand the general principle, method pointers become less terrifying.

This is the most complex syntax I have found in C++ so far. Do you know something even worse? Post it in the comments!

The pros and cons of each, and why you should consider Python for embedded programming.


via: https://opensource.com/article/21/2/ccc-method-pointers

作者:Stephan Avenwedde 选题:lujun9972 译者:译者ID 校对:校对者ID

本文由 LCTT 原创编译,Linux中国 荣誉推出