Learn c++: Variable parameters from cocos2d-x...

Posted by tester2 on Sat, 05 Feb 2022 18:41:45 +0100

Sequence in cocos2d-x is a special action that can represent a sequence of actions. This action is usually created using code like this:

Action* sequence = Sequence::create(acttion1, action2, action3/*, action4, action5...*/, nullptr);

The number of parameters in parentheses is virtually unlimited, either three or more.

So how does this work?

Obviously, it shouldn't be in the form of overloaded functions, and no one wants to write a function with 1-9999 parameters all over.
By looking at the source code of the function in cocos2d-x, you can see that the parameters of the function have such parameters:

    static Sequence* create(FiniteTimeAction *action1, ...) ;

This ellipsis... is the key to the implementation, and this function can support an unlimited number of parameters.

So how do you use those parameters inside the function?

The list of arguments to a function does not contain those parameters, only one....
Do those parameters really come in when the function is called? If it comes in, where is it saved?
Calling a function is a stacking process, and the parameters of the function are stacked in turn, including those covered by..
So you only need to know the first address of some of the parameters that.. represent to read these parameters, and.. the parameters are required to be used so that you can no longer follow other parameters, so you can get the first address of a parameter and read all the parameter data as much as you want.
That is, the parameters are in, but the parameter data in the stack does not contain its data type, and the function parameters do not specify what type of data is in. To read the data correctly, the programmer must specify what type it is.
So there are these steps:

  1. Specify the first address of the argument that is overridden by.. and use it to obtain data for all arguments;
  2. The parameter types are explained in turn, and the data is read in turn according to that data type.
  3. Read, release the actual memory covered by...

Specific implementation method

Step 1 passes va_ The start macro implementation requires two parameters. The first parameter is a pointer to the parameter data that is overwritten by, usually a va_ A variable of type list (actually a char*), the second parameter is... The first parameter on the left, which corresponds to providing the first address of the parameter covered by..
Step 2 passes through va_ The Arg macro implementation also requires two parameters, the first is the pointer to the parameter data mentioned above, and the second is the type of parameter to take out. There is a return value, which is a parameter that is currently read out. You can only read one parameter at a time. If you want to continue reading, you need to call the macro.
Step 3 passes va_ The end implementation passes in a pointer to the parameter data.

The implementation code in cocos2d-x is given here:

Sequence* Sequence::create(FiniteTimeAction *action1, ...)
{
    va_list params;
    va_start(params, action1);//Step 1

    Sequence *ret = Sequence::createWithVariableList(action1, params);//Step 2

    va_end(params);//Step 3
    
    return ret;
}

/************************************Step 2 Detailed******************************************/
Sequence* Sequence::createWithVariableList(FiniteTimeAction *action1, va_list args)
{
    FiniteTimeAction *now;
    FiniteTimeAction *prev = action1;
    bool bOneAction = true;

    while (action1)
    {
        now = va_arg(args, FiniteTimeAction*);//Specify a data type of FiniteTimeAction*
        if (now)//If read
        {
            prev = createWithTwoActions(prev, now);
            bOneAction = false;
        }
        else//If you have finished reading
        {
            // If only one action is added to Sequence, make up a Sequence by adding a simplest finite time action.
            if (bOneAction)
            {
                prev = createWithTwoActions(prev, ExtraAction::create());
            }
            break;
        }
    }
    
    return ((Sequence*)prev);
}

A few small questions

  • Va_as mentioned above Start needs to provide...the first parameter on the left, whether it can't be a function of pure...as a parameter, like this: void fun(...); But this way of writing and compiling is done, so va_ What is the second parameter of start to write? How can we get the parameter data correctly? In this case, the first address of the parameter should be the first address of the parameter that is overridden, but how can this be explained?
  • If you just look at the code above to understand va_ The use of Arg may be biased. I thought at first that if the parameter was read, it would return null (which is not reasonable in itself). Later I learned that va_ The Arg itself cannot tell you if the parameters have been read, so you need to manually tell you how many parameters there are. There are two ways to do this. One is to add a parameter to illustrate the number of parameters in... Another way I think it would be better to specify an end flag beforehand, and then make sure that it is used as the last argument when the function is called. So when va_ When Arg reads the end flag, it knows it's done. This is the case with cocos2d-x, which requires that the last parameter be nullptr when using the Sequence::create() method.

summary

... It feels very primitive and unsafe to use it honestly, or use it carefully.

Many of my understandings may be inappropriate or wrong. I welcome criticism and correction.

Topics: C++ cocos2d