Ref* pSender when and why?

Can someone tell me difference between when and why Ref* pSender is needed?

I’ve noticed that following is the combination.
Whenever I used with CC_CALLBACK, I’ve to use Ref* pSender in the function parameter.
But if we see CC_CALLBACK macro, it is also simply std::bind

MenuItemImage *settingsBtn = MenuItemImage::create("path1.png", "path2.png", CC_CALLBACK_1(HelloWorld::callBack, this));

void HelloWorld::callBack(Ref* pSender) { }
MenuItemImage *settingsBtn = MenuItemImage::create("path1.png", "path2.png", std::bind(&HelloWorld::callBack, this);

void HelloWorld::callBack() { }

Thanks :slight_smile:

I bet @grimfate has an amazing answer for this :slight_smile:

haha I’m not sure about that. Unfortunately I’m not an expert in this area and it it seems a little difficult to figure out exactly what is happening here. But I’ll try explain.

So, MenuItemImage::create can be provided a callback function and that callback function is a ccMenuCallback&, which is a typedef for std::function<void(Ref*)>. As you have noted, CC_CALLBACK_1 is just a macro for a std::bind call. So when the callback is set for your second code example, it is basically doing this:

std::function<void(Ref*)> callback = std::bind(&HelloWorld::callBack, this);

For some reason, even though HelloWorld::callBack (in the second example) takes no parameters and the callback is called with a Ref* object as a parameter (_callback(this); in the MenuItem class), this is still valid code that compiles and works.

So I assume what is happening is that the code is smart enough to pass the parameters that the function will accept and then discard the rest. For example, std::function<void(int, int)> func = std::bind(&HelloWorld::callBack, this, std::placeholders::_1); works with HelloWorld::callBack(int). When the function is called, e.g. func(100, 200), the first parameter (100) is passed to the function and the second parameter (200) is just ignored. So when you do this with your MenuItemImage callback, the Ref* object that is sent is just ignored.

But using std::bind like this doesn’t seem like a very good idea. It sounds like the sort of thing people would say is a “bad coding practice”. I don’t know if there are any actual problems with using it, e.g. worse performance, but at the very least it hides the fact that the function is being sent a Ref*.

An example of why this is bad is that I think that the point of the Ref* parameter is that it is providing you the MenuItem object that is making the callback, allowing you to easily know which object sent the callback and to allow you to easily use that object. If you use the std::bind way so your function doesn’t have to take a Ref* then you might forget that the Ref* object is being sent. If you do forget, if you ever need to know which object sent the callback, you may use some other, less convenient and/or less efficient way to figure out which object sent the callback. But that’s just what I think.

2 Likes

Would you mind if I used parts of your explanations to enhance the Programmers Guide? What I really like is that I can pretend to know nothing, read your text and come out ahead in understanding.

Go for it. I don’t mind at all. The more I can help people learn, the better.

@grimfate, thanks for the explanation. According to your reply, is the pSender in the code below represent for the Node mFamilyTV ?

mFamilyTV = MenuItemImage::create("en_block5.png", "en_block5_hover.png",
		[=](Ref* pSender) {
		auto jump = JumpBy::create(1.0, Vec2(0, 0), 100, 1);
		mFamilyTV->runAction(jump);
	});

If it does then how to use it? I try to replace mFamillyTV->runAction with pSender->runAction but there’s no such function in Ref class.

And Could you please list some usages of pSender . It would be great :slight_smile:

pSender is a Ref object, which means you can do little with it other than retaining and releasing. You need to cast it to a more useful type of object. Something like:

auto familyTV = (Node*)pSender;
auto jump = JumpBy::create(1.0, Vec2(0, 0), 100, 1);
familyTV->runAction(jump);

I don’t use menu items so I’m not sure what the uses of pSender are, but I assume it is so that if you use the same function with multiple menu items then it is a way to identify when. (And you might not have to capture anything with the lambda function.) For example,

auto func = [](Ref* pSender) {
        auto node = (Node*)(pSender);
        auto jump = JumpBy::create(1.0, Vec2(0, 0), 100, 1);
        node->runAction(jump);
};
    
auto item1 = MenuItemImage::create("en_block5.png", "en_block5_hover.png", func);
auto item2 = MenuItemImage::create("en_block5.png", "en_block5_hover.png", func);

auto menu = Menu::create(item1, item2, NULL);
this->addChild(menu);

In this example, the function is shared between 2 different menu items, so when it is called, there is no way for the function to know if it was called by item1 or item2 without using pSender.

And if you need to identify which menu item is calling the function, you can set a unique tag for each menu item and then check the tag inside the lambda function. Or you could capture the scene or menu items and compare the objects directly with pSender.

3 Likes

You save my day. Very detailed and comprehensive, thanks in advanced :smiley: