4

Currently I am working on a cocos2d+Box2D project so I have deal with some Objective-C++ code.

And I am facing to such situation:

#import "cocos2d.h"
#import "Box2D.h"

@interface BasicNode : CCNode {
@private
    ccColor3B _color;
    b2Body *_body;
    b2Fixture *_shape;
}

b2Body and b2Fixture are C++ class that defined in Box2D.h

It works if the implementation of BasicNode is named BasicNode.mm.

But if I have another file named Game.m that is using BasicNode and import BasicNode.h, it won't compile because .m file is Obj-C file and does not know about C++ code.

So I decided to move #import "Box2D.h" into implementation file and only keep type declaration in head file (this is exactly what header file should contain).

But how do I do it? They are C++ class type but they are actually just a pointer so I wrote some helper macro

#ifdef __cplusplus
#define CLS_DEF(clsname) class clsname
#else
#define CLS_DEF(clsname) struct clsname; typedef struct clsname clsname
#endif

CLS_DEF(b2Body);
CLS_DEF(b2Fixture);

It works, only if CLS_DEF(b2Body) is appear once only. Otherwise compiler will find multiple type declaration for a same name even they are the same. Than I have to change to

#ifdef __cplusplus
#define CLS_DEF(clsname) class clsname
#else
#define CLS_DEF(clsname) @class clsname
#endif

And it is working now.

But I don't think it is a great idea that I declare a C++ class type as an Obj-C class especially I am using ARC.

Is any better way do deal with it? And I don't really want to make something like this

@interface BasicNode : CCNode {
@private
    ccColor3B _color;
#ifdef __cplusplus
    b2Body *_body;
    b2Fixture *_shape;
#else
    void *_body;
    void *_shape;
#endif
}

Edit: Also please tell me will my tweak way introduce any problem?? by making C++ class ivar looks like Obj-C class for other pure Obj-C code.

Bryan Chen
  • 45,816
  • 18
  • 112
  • 143

3 Answers3

2

One simple solution is to rename Game.m to Game.mm.

Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
  • Yea..it will do. I just have to remember to make every .m file to .mm file instead. – Bryan Chen Nov 18 '11 at 10:49
  • Exactly, once you use a C++ header it will likely "spread" to other classes, so it's good practice to simply use .mm as the default extension for all ObjC classes from then on. – CodeSmile Nov 18 '11 at 11:18
  • 1
    Note that including C++ headers "everywhere" can slow down compilation somewhat. Depends on the size of the project, complexity of the header files (STL, boost and similar are the worst) and the speed of your computer. – pmdj Nov 18 '11 at 16:44
  • @LearnCocos2D: There is no reason why it needs to "spread" if you use headers right. Headers generally do not have to import other headers. The only cases when a header needs to import another header is when you subclass a class declared in another header, or implement a protocol declared in another header, or use a typedef or enum or struct defined in another header. These are not common cases. Most of the time, headers do not import other headers. And only implementation files that use the C++ headers need to be made C++. – newacct Feb 20 '14 at 01:47
  • Correct. What I meant is if someone doesn't know what you said and how to use forward declarations it's all too easy to import headers everywhere and run into the same issue over and over again. – CodeSmile Feb 20 '14 at 09:42
1

There are a couple of ways. If you can rely on using the Objective-C 2.2 runtime's features, you can add ivars in class (category) extensions. This means you can add ivars in your class's .mm file, and keep the .h file clean of any C++ stuff.

If you need to support older versions of the runtime, there are a few ways to do it which are better than #ifdefing. In my opinion, the best way is to use the 'pimpl' idiom which is common in C++ - you forward declare an implementation struct in your header, and add an ivar which is a pointer to such a struct. In your class's implementation (.mm), you actually define that struct with all its C++ members. You then just need to allocate that implementation object in your init... method(s) with new and delete it in dealloc.

I've written up the pimpl idiom as it applies to cleanly mixing Objective-C and C++ in this article - it also shows some other potential solutions which you could consider.

pmdj
  • 22,018
  • 3
  • 52
  • 103
  • Looks good except no more synthesize property for C++ type member. I still prefer my tweak way if it does not introduce new problem. – Bryan Chen Nov 18 '11 at 20:35
  • You can still synthesize the property for the PIMPL type. Treating a pointer to a C++ object as a pointer to an Obj-C class seems like a bad idea, but you might be okay as long as you don't use ARC. – pmdj Nov 20 '11 at 18:43
0

With Xcode 5, you don't have to declare instance variables in the header file, you can just declare them in the implementation file. So your BasicNode header file is not "contaminated" with C++.

You can use "struct" instead of "class" in C++. The only difference is that in a class all members are private by default, while in a struct they are public by default. But you can do everything with a struct that you can do with a class. That way you can write for example

struct b2Body;
struct b2Fixture; 

outside your interface, and

{ ...
    struct b2Body* _body;
  ...
}

in your interface.

gnasher729
  • 51,477
  • 5
  • 75
  • 98