Proposal for v3.0: "protect" the constructors

Xavier Arias wrote:

`Minggo Zhang
Of course that’s a problem! At least with some of the most basic cc types (CCPoint, CCSize, CCRect, CCString…) it’s imperative to keep it.

The way I see it, you should only protect CCNode’s and CCNode’s subclasses constructors.
The rest of classes can be used at the stack like temporary variables.

Even collections like CCArray could be useful in some cases to use inside methods as local variables.
You must note that all std classes support this. Value types is one of the core features of C++.

Point, Size, String,… won’t have protected constructors.

But I like what you said, it is much more precise: Only Nodes and its subclasses should have protected constructors… if so, they should also have protected destructors and `init@s.

More thoughts:

What about that:

  • if the object is subclass of Object (it should be RefCount ), then createXXX should be the only way to create it.
  • else, it should be able to be created in the stack.

Currently POD classes like Point, Size, etc… can be created in the stack.
Probably Array, Dictionary, String should not inherit from Object as well, and they should support templates as well.

So, the API will be easier to use:

  • Nodes, and subclasses, are RefCounted objects and must be created with createXXX
  • PODs and containers objects are not RefCounted, and don’t have createXXX.

Example:

void somefunction() {

// array is not Ref counted. Can be create with constructor.
Vector array;

// actions are ref counted: use "create"
auto action1 = Action::create(...);
auto action2 = Action::create(...);

auto sequence = Action::create( array );
array.addObject(action1);
array.addObject(aciton2);

// Sprite is ref counted. Should be created with create
auto sprite = Sprite::create(...);

sprite.runAction( sequence );

This look almost 100% right for me :slight_smile:
But we should also take a look at all the other classes and see whether they can be used properly at the stack or not.
For example, I remember using CCSpriteFrame and CCImage on the stack as temporaries as well.

Ricardo Quesada wrote:

More thoughts:
>
What about that:
>
* if the object is subclass of Object (it should be RefCount ), then createXXX should be the only way to create it.
* else, it should be able to be created in the stack.
>
Currently POD classes like Point, Size, etc… can be created in the stack.
Probably Array, Dictionary, String should not inherit from Object as well, and they should support templates as well.
>
>
So, the API will be easier to use:
>
* Nodes, and subclasses, are RefCounted objects and must be created with createXXX
* PODs and containers objects are not RefCounted, and don’t have createXXX.
>
Example:
>
[…]

How about going in the completely opposite direction and making the constructors the new preferred usage?

So …
* Constructors do not set autorelease.
* Deprecate the createXXX methods. They would still be autorelease.
* Smart pointers use retain/release (“hybrid”).
* An autorelease pool would just be a vector of AutoRefCount…

So the ‘correct’ usage would look like
@
AutoRefCount mSprite;
. . .
mSprite = new CCSprite(); // sprite is retained by the autorefcount
sprite->initXXX(…);
@
or
@
mSprite = new CCSprite(fileName) // sprite is retained by the autorefcount
@

Advantages:
* No compiler flags. New code and legacy code would both work, so you can port slowly.
* Auto ref count pointers are safe and fast
* CCObjects can be created on the stack. There is no need to distinguish between objects that can and can’t be created on the stack.
* It’s a C++ pattern. The createXXX pattern comes from Objective C.

On the downside you still need to call initxxx() and might forget :wink:

@Paul:
I’m currently using Cocos2d, and I’m totally new to Cocos2d-x. I’ve only taken a short look today. Do you mean that the last red row would be the future syntax, and that the first red block could be removed after a while? If so, I think it’s looks great. I don’t understand the reason for this factory pattern with init etc…

To me, all aspects of the Objective C legacy turns me off. I will not move to Cocos2d-x unless it is pure C*+ code. I think Objective C is ok, and I want Cocos2d to be written in Objective C using Objective C syntax, and I want Cococs2d-x to be written purely in C*+ syntax.

C*+ gives flexibility. When I’ve looked in these threads there has been lots of discussions about performance issues with smart pointers. I think that the focus shall be on finding out the correct C*+ way of doing it, instead of implementing our own solutions. That will make the engine attract C*+ competence, and in the long run that might lead to performance boosts. I think that the current engine turns off C*+ gurus, and the community needs them.

Ok, I want to be clear that I’m definitely not a C*+ guru, but one reason to switch to cocos2d-x is that I think it’s more versatile to learn C*+ compared to learning Objective C.

Hi Fredrik,

You will find the create pattern in many C++ libraries.

In cocos2d-x is used to return errors instead of throwing exceptions.

Thanks Riq!

Is there any advantage of “instead of throwing an exception”? Exception don’t have much overhead. What about your class have a const reference member? new & init is not suitable for C*+ because it has RAII. In C language, if init return false, user can do some release work, that’s OK . C*+ has RAII, if you code in right way, all resource clean are auto. “new & init” is a fake C*+ code, also the “do while(0)”, they are not C*+ philosophy.

Also, factory pattern are useful because its create function can have different version according to different parameter combination. But you could not write this factory in advance, because you don’t know what the parameter will be(user might derive class in cocos2dx). A dummy create function just a syntax sugar.

Besides this topic, there are some other also let a C*+ coder uncomfortable:

  1. CCCallFunc. If time goes back ten years, this is ok. But nowadays C*+ have function and bind. The main disadvantage is: 1, can not pass variable on stack. 2, can not bind whatever coder wants to, too much limitations. 3. might don’t work in multiple inheritance.
  2. Memory management. I can accept this because the characteristic of cocos2d: a tree of CCNode represent whole program. For objects in View layer, disconnect from this tree means the it will not use again. But in Model layer I think few people will use autorelease & Containers defined in cocos2dx. However “find another way” in a language will bring problems, most of them can not be fixed properly. For example, autorelease brings this topic: how to automaticlly call init & autorelease after ctor calls and not violate other things in Cpp(like init const reference member)? I think this will not be fixed properly due to Cpp is not supposed used as this way unless use smart ptr instead. That will make things return to pure C*+ way, and problems relate to this will gone.
  3. init function. This is a poor usage of C**. Do you have seen this usage in any top-class level C*+ code such as STL, boost? Return something to indicate error is bad for object init. This is a miss use of C*+ and it is brings many other problem for example this topic you are discussing. Try to use self defined way to run like ctor and not bring problem is impossible. The ctor, RAII, stack unwind is core concept of C*+. In practice view: what can you do when a init return false? Nothing. You might say it needs to release some resource, but that is no help to make your program to work right and OS will reclaim memory when process terminate. init function really meaningless, it just pretend to coding strictly.
  4. Using template method as possible especially for onXXX virtual functions. MyLayer::onEnter(){…; CCLayer::onEnter();} User can not forget call CCLayer::onEnter(). This is not convenient, define a private virtual void onEnterAction() = 0 in base class.

Ning li, android dont recommend throwing an exception and i doubt they even have try catch.

  1. CCCallFunc is going to be replace with function and bind in cocos2d-x 3.0
  2. autoRelease is under discussion of using shared_ptr instead.
  3. I dont find it inconvenient but ofcourse just me. So no idea.

Well, correct me if I’m wrong, I might had given wrong information.

Lovepie Madzech,
class A {
public:
void f() {before_f_action();do something}
private:
virtual void before_f_action(){}
};
class B : public A {
private:
virtual void before_f_action(){ do something additional }
};

Instead simply make f be virtual, this is more serious and grace because something(such as CCLayer::onEnter) should not open for user.

Well ning li, if you look at the level of inheritance, onEnter actually is required to be modified on each level. if CCNode onEnter is not open for user, then CCLayer onEnter will not be able to execute. And as a user, cocos2d-x simply isn’t enough to make real games yet, there is much more level of inheritance that might be required and different level of onEnter needs to be modified.

In my opinion, to put it simple, and yet to support flexibility. onEnter will be separated into beforeOnEnter, onEnter, and afterOnEnter called by enter() function.

class A
{
public:
virtual void enter()
{
beforeOnEnter();
onEnter();
afterOnEnter();
}
virtual void beforeOnEnter(){}
virtual void onEnter(){ /* User expected to modify here only. */}
virtual void afterOnEnter(){}
};

This increased all function into 4 fold on a class considering there are setPosition, setScale, setAnchorPoint and many many more.

Well, correct me if I’m wrong, I might have get the wrong point and provided wrong information.

Lovepie Madzech,

  1. NDK support exception since r5. There is no good result when miss use C++ (I mean disable ctor in C++, use init, create instead), whatever you try to do.
  2. For a view of lib or framework, hide implementation from user will have stronger contract. Do what user want to do but if there are something goes wrong because using improperly are not my business is not so serious. A good implemented lib will reduce user goes wrong as possible as it can. That’s why there is design pattern.
    The key point is make function content into 2 parts: one is user should not rewrite(the f in my example, it is not virtual), other is user can refined(the before_f_action) but it is private because it is part of ‘f’, call it directly is meaningless. And you said CCLayer needs call CCNode’s onEnter, that is alright, because user don’t care codes in CCLayer’s onEnter. So call CCNode::onEnter in CCLayer::onEnter is totally OK. The key is: for the actions might refined by users, not just simply let them override onEnter. Refer Template Method Design Pattern.

For your second point, I dont think there will be any part of the code that could possibly never be modified, do you?

We use library or framework because they implement a kind of requirements which are common for all scene. We use them but don’t need to care how it work. If there is a library or framework you use requires you know everything inside it, that indicates: the business it want to implements are too hard to abstract. Or, it just sucks.
If there is a library or framework, any piece of code might require user involved, what is the different of implement it from scratch by user? So there must be large parts are “should not touch”. That is the signification of a library to exist. If not so, it is useless.

Making a class flexible with virtual still does not require you to know everything inside, it just makes you have the flexibility to choose to modify it through extensibility.

The day a developer choose to use an open source software, engine, library, or even framework is because he wanted to know what is happening inside and hopefully gain as much control with it than the close source ones. Making something untouched simply require that developer to modify it from the library itself and updating it later will be troublesome.

Finally, as people said, why choose open source when you do not even wanted to care what is happening inside? I doubt it wasn’t simply because it is cool nowadays to use an open source.

Take for example your first point about throwing an exception, using return or throwing exception has big war going on till today and no single side has won, you may think exception throwing is a suitable method while others simply think return method does. But since it is open source, you can change to exception throwing yourself anyway.

To clear a misunderstanding here, I wasn’t trying to say everything should be virtual but the current way is quite reasonable although there are some part where I do disagree as well.

Lovepie Madzech wrote:

The day a developer choose to use an open source software, engine, library, or even framework is because he wanted to know what is happening inside and hopefully gain as much control with it than the close source ones.

_“If there is something wrong — just have a look inside The first and best reason for open source is actually the same as for close source: It solves a given problem. Followed by the support (commercial support available? how big is the community? how active is the product? how good is the documentation?) and many more points. Whereas the price tag is especially important for indie developers.
But “It’s very likely for me to break the API and then it’s important to have the ability to dig inside the beast” is, IMHO, not the first selection criteria — if ever. I understand ning li’s point very well. The library should be user friendly and stable as even possible! And to stick with objective-c patterns is, at least for me, not the best ground / standpoint for a c*+ library.

Take for example your first point about throwing an exception, using return or throwing exception has big war going on till today and no single side has won, you may think exception throwing is a suitable method while others simply think return method does. But since it is open source, you can change to exception throwing yourself anyway.
Yes, it’s not easiest question to ask. But it is a good and important question to ask! Why? Simply because this is a C*+ library. And”it’s open source, you can change it" is not a very qualified answer. Because, in this case, it’s a very low-level detail and it’s no easy thing to change it. Yes, in theory I’m able to restructure everything to get rid of the centralized CCDirector … but … well … :wink:

I’d love to see this community to be more open for such questions / decisions. Especially in the current state with 3.0 - we have the ability to make big changes! If "get rid of objective-c and embrace c11" is one of the main goals for 3.0 — we must ask such questions!
Embrace C
11! Embrace modern design patterns! Embrace existing libraries and don’t invent everything again! Focus on the core and the community will follow / do the rest! That’s my opinion :slight_smile:

Lovepie Madzech,
Modify a open source lib is not a industrial behavior, because that is costing and your boss will kick your ass. Ask yourself, will you modify codes in STL? That’s not a good idea. You can learn in it, but if there is something does’t satisfy you requirement, post a improvement requirement to dev team is best choice. Handle it by self means two things:1, you usage is not supposed. 2, nobody maintain the project now (I never use this, too risky).

For the exception problem, notice that I said return something is bad for OBJECT INIT. That is no doubt in C**. Read this: ]. You need to know exception has two kinds: one is logic, coder can do something to recover such as pointer parameter is NULL, other is runtime . A runtime exception throws means coder can not control, such as out of memory, disk full… Because ctor has no return value, the only way to report error is throw exception. If you catch it, the stack unwinding will make resource clean automatic. More important, a lib throws exception does not mean you must cope with that, you can: 1, tell compiler disable that. 2, just don’t catch anything. If a exception throwing, abort will be called. This is reasonable for runtime exception: the only thing you can do is terminate the process. For the logic exception, unlike java, few c** people use exception.

Two phase init is a miss use, but cocos2dx goes further: they try to encapsulate these two phase into one function! That’s very embarrassing, because what they want to do are making a self-defined ctor in C++.

Michael Contento,
That was just an example. Myself do support exception throwing to a certain degree and has not much comment however exception throwing as said must serve its purpose well. If the exception has its true meaning and true usage, then why not? The current create method has 1 more benefits which is the autoRelease calls and is currently at its most suitable place until ARC is being replaced with another model.

ning li,
Modify part of a code does not necessary because the usage is not right. There will always be a chances that a calculation is too heavy and you wish to simplify it.

The worst part I could say for a closed source is game developer has to wait for new releases to get new features. Not all company can wait to submit some requirement and wait until it is approved and added in or finally rejected and changes to other game engine. When there is a requirement but modification is not allowed is simply wasting time. The best way to suggest a modification or new feature is to first implement it and tested it. There has been a few suggestion and features that have merged into cocos2d-x simply because the developer did so and has been found useful.

Good points, ning li!

I’ve read a few threads in this forum since my last post, and I’m glad to see these kinds of discussions in lots of threads. I’m not experienced enough in c++/cocos2d-x to judge what is the best solution, but I do think that this kind of discussions are vital. Keep it up!

Regarding the lib thing, I think that Michael Contento & ning li are right. It is vital that the interface of the lib is consistent, but generally the user shall not have to bother with the functionality inside. Then there are of cause users that do make changes and submit them to benefit to both themselves and the community.

Keep discussing. I’m learning! :wink: