📜 ⬆️ ⬇️

Ways to embed C ++ in Objective-C projects

This topic is my translation of an article about how to embed C ++ in Objective-C projects, which tells some interesting solutions, both successful and unsuccessful. Original article:
www.philjordan.eu/article/strategies-for-using-c++-in-objective-c-projects

So,

Ways to embed C ++ in Objective-C projects


If you are in a hurry and want to go straight to solving the problem of embedding C ++ objects into Objective-C classes without corrupting header files, so that they can be included from pure Objective-C, you can scroll through the article to the Pimpl heading. This solution can be used in ~ 95% of cases. The remaining part contains a deeper analysis of the problem and additional methods for solving it.

Why mix Objective-C and C ++?


Using Objective-C, usually for programming for iOs or for Mac, I often encountered situations when it was necessary to insert C ++ into a project. Sometimes the best library for the current task was written in C ++, sometimes the solution to the problem could be more succinctly done in C ++. The most commonplace example is C ++ templates that save you from writing repetitive standard code. Less obviously, Objective-C is sometimes too object-oriented. This sounds like a heresy for “people-for-whom-each-object-is-object”, but for non-trivial data structures I often find the object orientation too cumbersome, and Critical structures too weak. Using the same C ++ is just right.
')
Objective-C is also quite aggressive in terms of memory management when, for example, there is no garbage collector. STL (and its new shared_ptr extension) lets you forget about this problem, or at least focus it on constructors / destructors, rather than cluttering up the code with release'ami and retain'ami. Of course, this is a matter of taste, and depends on the situation; automatic memory management helps in code with non-trivial data structures or in algorithmically complex code.

Another reason for mixing Objective-C and C ++ is the opposite situation when you need to call Objective-C functions from the C ++ project. A common example is porting a game or engine for Apple platforms. In such cases, you can also use the techniques described below.

Finally, you can use C ++ to improve performance. The flexibility of Objective-C messages imposes some costs compared to the implementation of most of the virtual functions of C ++, even taking into account the caching techniques that are used in modern runtimes. Objective-C objects do not have equivalent fast non-virtual C ++ functions. For optimization this can be an important factor.

Leading to a common denominator: C


One of the possible ways to use these two programming languages ​​in one project is to completely separate them, allowing them to interact through pure C. In this way, their mixing can be prevented. It will look like this: the code that uses the C ++ library is transferred to the .cpp file, the interface is declared in the C header file, the C ++ part implements this interface using extern "C" functions, and the code that will access the interface C is pure Objective-C (.m).

This will work well in simple cases, but it is more likely that you will have to write some shell. Anyone who has the experience of dynamically loaded C ++ libraries using the open C interface knows this well.

Today, virtually the entire Objective-C is compiled using GCC or clang. Both compilers support Objective-C ++, which means that there is a more convenient way to mix languages.

Objective-C ++ and problems with header files


At first glance, the use of Objective-C ++ dialect looks like the most straightforward approach. This is the result of merging C ++ and Objective-C together in a single compiler, reliable implementations of which are in both GCC and clang. Considering how different Objective-C and C ++ are, the GCC programmers did the hard work. But as soon as you start renaming .m files to .mm to declare them as C ++ files, it comes to the realization that everything is not so simple.

Header files and the C preprocessor over many years have caused C, C ++, and Objective-C programmers a headache, and mixing these languages ​​makes things worse. Suppose you need to use the map container (dictionary, associative array) from the STL library in an Objective-C class. As far as I know, the Apple Foundation framework does not contain a sorted map structured in trees. So, create a member variable in our class:
#include <map> @interface MyClass : NSObject { @private std::map<int, id> lookupTable; } // ... @end 

However, std :: map <int, id> makes sense only for a compiler that supports C ++, and only after #include, so now this header file can be imported (#import) only from Objective-C ++ files. And any code using this class should now be converted to Objective-C ++. Further, along the chain, the remaining header files will also have to be converted (.mm), and so with the entire project.

In some cases this is permissible. Despite this, it is too cumbersome and redundant to change the whole project or a large part of it only in order to use the library in one place; In addition, if you are the only one in the project among Objective-C programmers who know C ++, this will not be a very good idea. In addition, problems may arise when compiling code on a pure C compiler with C ++, it rarely happens that this is completely painless. Moreover, this means that the code cannot be reused automatically in other Objective-C projects.

In most cases, the solution that allows you to take advantage of the code in pure Objective-C or C ++ is to use Objective-C ++ where and only where necessary. To do this perfectly, will have to try.

Shooting your own leg: void *


It becomes clear that the goal is to remove everything from C ++ from header files. The typical way to hide a type is a pointer to a void. Here, of course, this will also work:
 @interface MyClass : NSObject { @private //    std::map<int, id>* void* lookupTable; } // ... @end 

In those parts of the code where the table will be used, you will have to use an explicit type conversion: static_cast <std :: map <int, id> *> (lookupTable) or ((std :: map <int, id> *) lookupTable), which will be very annoying. If the actual type of a member of a class changes, all type conversions will have to be changed manually, and this dramatically increases the probability of making a mistake. As the number of members grows, it becomes impossible to remember all the correct types, and as a result we get the drawbacks of both static and dynamic typing. Using this method to work with objects from the class hierarchy is a dangerous game with fire, in which it may also turn out that the A * and B * pointers to the same object have different representations of void *

You can do better.

Conditional compilation


Loss of type information is bad, but since C ++ typed fields are used from Objective-C code, and a pure Objective-C compiler needs to know only about their presence (for correct memory allocation), why not make two different versions of code ? Objective-C ++ defines the _cplusplus preprocessor symbol, so how about this implementation:
 #ifdef __cplusplus #include <map> #endif @interface MyClass : NSObject { @private #ifdef __cplusplus std::map<int, id>* lookupTable; #else void* lookupTable; #endif } // ... @end 

Ugly, but it is much easier to work with. Standard C ++ does not guarantee that a pointer to a class and a pointer to void will have the same location in memory (the object is, of course, one), but Objective-C ++ is not a GNU / Apple standard. In practice, the only problem is pointers to virtual functions when converting to void * occurs, and when you try to do this, the compiler will swear loudly. If the anxiety does not go away, you can use static_cast <> instead of the C-style cast.

Since it still successfully leads void * to any other pointer in an implicit way, therefore, it might be preferable to replace the Sich part with #ifdef with a pointer to a structure with a complex but unique and recognizable name, for example, struct MyPrefix_std_map_int_id. You can even define a macro that expands to the correct type definition depending on the compiler:
 #ifdef __cplusplus #define OPAQUE_CPP_TYPE(cpptype, ctype) cpptype #else #define OPAQUE_CPP_TYPE(cpptype, ctype) struct ctype #endif // ... #ifdef __cplusplus #include <map> #endif @interface MyClass : NSObject { @private OPAQUE_CPP_TYPE(std::map<int, id>, cpp_std_map_int_id)* lookupTable; } // ... @end 

With this method, it will not be possible to avoid the conditional #include C ++ headers, and this can confuse people who do not know or dislike C ++. Yes, and it does not look very much. Fortunately, there are other solutions.

Abstract classes, interfaces and protocols


Many C ++ programmers are familiar with purely virtual functions, and therefore, abstract classes. Other languages, such as Java and C #, have an explicit "interface" concept, in which implementation details are intentionally hidden. The same pattern can be used in our case.

Recent versions of Objective-C support protocols similar to Java / C # interfaces in essence, if not in syntax. You can declare public methods of a class in the protocol in the header file, and declare and write a specific class that implements the specified protocol in closed code (thus dividing C ++ and Objective-C). This will work for instance methods, but you will not be able to directly create an instance of a class through a protocol. Therefore, it will be necessary to delegate the memory allocation and initialization of new objects to the class factory or the C function. Worse, the protocols are in some sense orthogonal to the classes, so when the links are declared, they will look different:
 id<MyProtocol> ref; 

instead of the expected:
 MyClass* ref; 

Everything works, but this is not ideal.

Abstract classes in Objective-C


So what about abstract classes? There is no direct idiomatic support for them in this language, but even the common NSString is abstract, and it is impossible to understand this simply by using it. One alternative way to solve our problem is to take all the method declarations and put them all into this class. We'll have to endure compiler warnings about an incomplete class definition. In runtime attempts to call a non-existent method will throw exceptions. You can, of course, create pseudo-implementations of these methods that will throw special exceptions explaining the situation.

In most languages, to create instances, you need to know a particular class, or delegate it to a class factory. Interestingly, you can directly send to the NSString class messages alloc and init, and get instances of classes inherited from NSString. The init messages return an object other than self that init was sent to, for example, the NSString can be redefined at NSSFString, the class factory is NSString. If you do this yourself, you will have to define all the init * methods used by a particular class for the abstract, otherwise they will not be visible to those who use it.

Thus, to declare all the methods in an abstract class, following the Objective-C ++ classes from it, is definitely the most dust-free solution for the header file and for those who use this class, but at the same time it is the most time-consuming, requiring an additional class, pseudo-methods and nontrivial init implementations.

However, C ++ programmers found an elegant solution to similar problems. One of the serious problems of large C ++ projects is the dramatically increasing compilation time associated with header file dependencies. In such cases, hiding the internals of the class from the programmers using it is usually done. Exactly the same solution can be applied to the Objective-C / C ++ dilemma.

Pimpl


Pimpl is short for “pointer to implementation” (pointer to implementation) or “private implementation” (private implementation). This idiom is pretty simple. In the open header file, a forward description of the implementation of the struct is added, usually using the name of the open class with the suffix Impl, depends on the agreement. This struct will contain all the members that need to be hidden from the public class header file. It remains to add a pointer to the structure in the class variables and define the structure members in the .cpp file (in our case, .mm). In the constructor (more precisely, in -init *), using the new operator, you need to create an instance of the structure, assign it to a class variable, and make sure that delete is invoked in -dealloc.
 MyClass.h: // ... struct MyClassImpl; @interface MyClass : NSObject { @private struct MyClassImpl* impl; } //   ... - (id)lookup:(int)num; // ... @end MyClass.mm: #import "MyClass.h" #include <map> struct MyClassImpl { std::map<int, id> lookupTable; }; @implementation MyClass - (id)init { self = [super init]; if (self) { impl = new MyClassImpl; } return self; } - (void)dealloc { delete impl; [super dealloc]; } - (id)lookup:(int)num { std::map<int, id>::const_iterator found = impl->lookupTable.find(num); if (found == impl->lookupTable.end()) return nil; return found->second; } // ... @end 

Everything will work, because the forward declaration of structures is valid C code, even if it later turns out that the structure contains either explicit or implicit C ++ constructors or even parent classes. The public methods of the class have access to the structure through a pointer, and the creation and deletion is done using new / delete.

It depends on the implementation how much more functionality the open class methods will have. You can improve performance by avoiding sending Objective-C messages in some cases, but it’s worth remembering the troubles that can arise when C ++ methods have to send messages to Objective-C classes.

It is worth noting that instead of implementing the structure in MyClass, you can inherit from it MyClass. So you can avoid indirect calls:
 @interface MyClass : NSObject { struct MyClassMap* lookupTable; } - (id)lookup:(int)i; @end and MyClass.mm: #import "MyClass.h" #include <map> struct MyClassMap : std::map<int, id> { }; @implementation MyClass - (id)init { self = [super init]; if (self) lookupTable = new MyClassMap; return self; } - (id)lookup:(int)i { MyClassMap::const_iterator found = lookupTable->find(i); return (found == lookupTable->end()) ? nil : found->second; } - (void)dealloc { delete lookupTable; lookupTable = NULL; [super dealloc]; } @end 

But with the increase in the number of members, this becomes impractical due to the large number of new / delete.

Restrictions


In pure C ++, implementation can be made a class, but in our case this will not work, since the forward declaration must be correct in Objective-C. In the C ++ literature, you can find recommendations for using shared_ptr <> and auto_ptr <> for automatic deletion of objects. In Objective-C header files, this will not work either. It is also not possible to get away from the pointer to the string because the compiler must know how much memory must be allocated for it.

The implementation is closed, so direct access to the members of the heir classes is not possible. However, it is possible to render structure declarations in a semi-private header file that will be included only by descendant classes that should have this direct access. Such heirs must be written in Objective-C ++.

Thoughts at the end


Still, I find the Pimpl idiom the best choice for embedding C ++ in Objective-C in almost all cases. Even if the structure contains only one member, the absence of unnecessary type conversions compensates for indirectness. Performance is not lost, as the structure itself is not a pointer. In the case when you need to enable Objective-C in C ++, you can do it in the same way: define the C ++ public interface, ahead of the declaration for class implementation in the header file, and put the definition of Objective-C members in the appropriate .mm file.

Source: https://habr.com/ru/post/137469/


All Articles