【IOS開發(fā)基礎系列 整理】OC與c++混編專題

1 objective-c和c++混合編程

1.1 OC調用C++類的方法

????????在 Objective-C++中,可以用C++代碼調用方法也可以從Objective-C調用方法。在這兩種語言里對象都是指針,可以在任何地方使用。例 如,C++類可以使用Objective-C對象的指針作為數據成員,Objective-C類也可以有C++對象指針做實例變量。下例說明了這一點。

????????注意:Xcode需要源文件以".mm"為擴展名,這樣才能啟動編譯器的Objective-C++擴展。

1.1.1 簡單示例

/* Hello.mm

* Compilewith: g++ -x objective-c++ -framework Foundation Hello.mm? -o hello

*/

#import <Foundation/Foundation.h>

class Hello {

????private:?

????????id greeting_text;? // holds an NSString

????public:????

????????Hello() {

????????????greeting_text = @"Hello, world!";

????????}

????????Hello(const char* initial_greeting_text) {

????????????greeting_text = [[NSString alloc] initWithUTF8String:initial_greeting_text];

????????}

????????void say_hello() {

????????????printf("%s\n", [greeting_text UTF8String]);

????????}

};


@interface Greeting: NSObject {

????@private

????????Hello *hello;

}

- (id) init;

- (void) dealloc;

- (void) sayGreeting;

- (void) sayGreeting:(Hello*)greeting;

@end


@implementationGreeting

- (id) init {

? ? if (self = [super init]) {

????????hello = new Hello();

????}

????return self;

}

- (void) dealloc {

????delete hello;

????[super dealloc];

}

- (void) sayGreeting {

????hello->say_hello();

}

- (void) sayGreeting:(Hello*)greeting {

????greeting->say_hello();

}

@end


int main() {

????NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

????Greeting *greeting = [[Greeting alloc] init];

????[greeting sayGreeting];? // > Hello,? world!

????Hello *hello = new

????Hello("Bonjour, monde!");

????[greeting sayGreeting: hello];// > Bonjour,? monde!

????delete hello;

????[greeting release];

????[pool release];

????return 0;

}

????????正如你可以在OC接口中聲明C結構一樣,你也可以在OC接口中聲明C++類。跟C結構一樣,OC接口中定義的C++類是全局范圍的,不是OC類的內嵌類(這與標準C(盡管不是C++)提升嵌套結構定義為文件范圍是一致的)。

????????為了允許你基于語言變種條件化地編寫代碼,OC++編譯器定義了__cplusplus和__OBJC__預處理器常量,分別指定C++和OC。??? 如前所述,OC++不允許C++類繼承自OC對象,也不允許OC類繼承自C++對象。

class Base {/* ... */ };

@interfaceObjCClass: Base ... @end // ERROR!

class Derived:public ObjCClass ... // ERROR!

????????與 OC不同的是,C++對象是靜態(tài)類型的,有運行時多態(tài)是特殊情況。兩種語言的對象模型因此不能直接兼容。更根本的,OC和C++對象在內存中的布局是互不 相容的,也就是說,一般不可能創(chuàng)建一個對象實例從兩種語言的角度來看都是有效的。因此,兩種類型層次結構不能被混合。

????????你可以在OC類內部聲明C++類,編譯器把這些類當作已聲明在全局名稱空間來對待。就像下面:

@interface Foo{

????class Bar { ... } // OK

}

@end


Bar *barPtr;// OK

????????OC允許C結構作為實例變量,不管它是否聲明在OC聲明內部。

@interface Foo{

????struct CStruct { ... };

????struct CStruct bigIvar; // OK

} ...


@end


????????Mac OS X 10.4以后,如果你設置fobjc- call-cxx-cdtors編譯器標志,你就可以使用包含虛函數和有意義的用戶自定義零參數構造函數、析構函數的C++類實例來做為實例變量 (gcc-4.2默認設置編譯器標志fobjc-call-cpp-cdtors)。OC成員變量alloc完以后,alloc函數會按聲明順序調用構造器。構造器使用公共無參數恰當的構造函數。OC成員變量dealloc之前,dealloc方法按聲明順序反序調用析構函數。OC沒有名稱空間得概念,不能在C++名稱空間內部聲明OC,也不能在OC類里聲明名稱空間。OC類、協(xié)議、分類不能聲明在C++ template里,C++ template也不能聲明在OC接口、協(xié)議、分類的范圍內。

????????但是,OC類可以做C++ template的參數,C++ template參數也可以做OC消息表達式的接收者或參數(不能通過selector)。


1.1.2 OC++類的頭文件定義

????????你可能會想使用等價的Objective-C類型和函數將C++代碼封裝(wrap)起來。比方說,你有一個名為CppObject的C++類(CppObject.h):

#include <string>

class?CppObject

{

??? ??public:

??????????void?ExampleMethod(conststd::string&?str);

??????????//?constructor,?destructor,?other?members,?etc.

};

????????在Objectiv-C類允許定義C++類的成員變量,所以可以首先嘗試定義一個ObjcObject封裝類(ObjcObject.h):

#import?

#import?"CppObject.h"

@interface?ObjcObject?:?NSObject?{

????CppObject?wrapped;

}

-?(void) exampleMethodWithString:(NSString*)str;

//?other?wrapped?methods?and?properties

@end

????????然后在ObjcObject.mm中實現這些方法。不過,此時會在兩個頭文件(ObjcObject.h&CppObject.h)中得到一個預處理和編譯錯誤。問題出在#include和#import上。對于預處理器而言,它只做文本的替換操作。所以#include和#import本質上就是遞歸地復制和粘貼引用文件的內容。這個例子中,使用#import "ObjcObject.h"等價于插入如下代碼:

//?[首先是大量Foundation/Foundation.h中的代碼]

//?[無法包含],因為它僅存在于C++模式的include?path中

class?CppObject

{

? ??public:

??????????void?ExampleMethod(conststd::string&?str);

??????????//?constructor,?destructor,?other?members,?etc.

};

@interface?ObjcObject?:?NSObject?{

????CppObject?wrapped;

}

-?(void)exampleMethodWithString:(NSString*)str;

//?other?wrapped?methods?and?properties

@end

????????因為class CppObject根本不是有效的Objective-C語法,所以編譯器就被搞糊涂了。 錯誤通常是這樣的:

Unknown type name'class'; did you mean 'Class'?

????????正是因為Objective-C中沒有class這個關鍵字.所以要與Objective-C兼容,Objective-C++類的頭文件必須僅包含Objective-C代碼,絕對沒有C++的代碼-這主要是影響類型定義(就像例中的CppObject類)。

1.1.3 簡潔頭文件——使用ivars

????????之前的文章已經提到一些解決方案.其中最好的一個是PIMPL,它也適用于現在的情況。這里還有一個適用于clang的新方法,可以將C++代碼從Objective-C中隔開,這就是class extensions中ivars的。

????????Class extensions(不要同categories弄混)已經存在一段時間了:它們允許你在class的接口外的擴展部分定義在@implementation段前,而不是在公共頭文件中。這個例子就可以聲明在ObjcObject.mm中:

#import "ObjcObject.h"

@interface ObjcObject() // note the empty parentheses

- (void)methodWeDontWantInTheHeaderFile;

@end

@implementation ObjcObject

//etc.

????????GCC也支持這個操作。不過clang還支持添加ivar塊,也就是你還可以聲明C++類型的實例變量,既可以在classextension中,也可以在@implementation開始的位置。本例中的ObjcObject.h可以被精簡為:

#import <Foundation/Foundation.h>

@interface ObjcObject : NSObject

- (void)exampleMethodWithString:(NSString*)str;

//other?wrapped?methods?and?properties

@end


????????去掉的部分都移到實現文件的class extension中(ObjcObject.mm):

#import "ObjcObject.h"

#import "CppObject.h"

@interface ObjcObject(){

? CppObject wrapped;

}

@end


@implementation ObjcObject

- (void)exampleMethodWithString:(NSString*)str

{

//NOTE: str為nil會建立一個空字串,而不是引用一個指向UTF8String空指針.

??? std::string cpp_str([str UTF8String],[str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);

??? wrapped.ExampleMethod(cpp_str);

}

????????如果我們不需要interface extension來聲明額外的屬性和方法,ivar塊仍然可以放在@implementation開始位置:

#import "ObjcObject.h"

#import "CppObject.h"


@implementation ObjcObject{

??? CppObject wrapped;

}


- (void)exampleMethodWithString:(NSString*)str

{

//NOTE: str為nil會建立一個空字串,而不是引用一個指向UTF8String空指針.

??? std::string cpp_str([str UTF8String],[str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);

??? wrapped.ExampleMethod(cpp_str);

}

????????定義的CppObject實例wrapped在ObjcObject創(chuàng)建時,CppObject的缺省建構函數會被調用,而在ObjcObject被調用dealloc析構時,ObjcObject的析構函數也會被調用。如果ObjcObject沒有提供缺省的建構函數,編譯就會失敗。

1.1.4 管理被封裝C++對象的生命周期

????????解決方案是透過new關鍵字掌握建構過程,比如:

@interface ObjcObject(){

??? CppObject *wrapped;//指針!會在alloc時初始為NULL.

}

@end


@implementation ObjcObject

- (id)initWithSize:(int)size

{

??????? self = [super init];

? ??? ??if(self)

??? ????{

??????? ????wrapped = new CppObject(size);

??????? ????if(!wrapped) self = nil;

??? ????}

??? ????return?self;

}

//...

????????如果是使用C++異常,也可以使用try {...} catch {...}把創(chuàng)建過程封裝起來.相應地,還要顯式地釋放封閉對象:

- (void)dealloc

{

??? deletewrapped;

??? [super dealloc];//如果使用了ARC,這句就要略去

}

????????作者接著提到了另一個方法,顯示分配一塊內存,然后在它的基礎上調用new來創(chuàng)建對象。首先聲明char wrapped_mem[sizeof(CppObject)];再使用wrapped = new(wrapped_mem) CppObject();創(chuàng)建了實例wrapped。釋放時if (wrapped) wrapped->~CppObject();這樣雖然可行,但不建議使用。


1.1.5 總結

????????一定要確保封裝的方法僅返回和使用C或Objective-C類型的返回值及參數。同時不要忘記C++中不存在nil,而NUL是不可用于解引用的。

1.2 在C++代碼中使用Objective-C類

1.2.1 簡單示例

????????這個問題同樣存在于頭文件中。你不能因為引入Objective-C類型而污染了C++頭文件,或無法被純C++代碼所引用。比方說,我們想封裝的Objective-C類ABCWidget,在ABCWidget.h聲明為:

#import <Foundation/Foundation.h>

@interface ABCWidget

- (void)init;

- (void)reticulate;

//etc.

@end

????????這樣的類定義在Objective-C++中是沒有問題的,但在純C++的代碼是不允許的:

#import "ABCWidget.h"

namespace?abc

{

??? class?Widget

??? {

??????? ????ABCWidget *wrapped;

??? ????public:

??????? ????Widget();

??????? ????~Widget();

??? ????????void?Reticulate();

??? };

}

????????一個純粹的C++編譯器在Foundation.h中的代碼和ABCWidget聲明位置出錯。

1.2.2 永恒的PIMPL

????????有沒有這樣的東西作為一類擴展C++,這樣的把戲將無法正常工作。 另一方面,PIMPL,工作得很好,實際上是比較常用的純C++了。在我們的例子中,我們減少到最低限度:C++類

????????C++并沒有之前提到的class extension,但是卻有另一種較為常用的方式:PIMPL(Private Implementation,私有實現)。這里,將C++class的定義精簡為:

namespace?abc

{

??? struct WidgetImpl;

??? class?Widget

??? {

??????? WidgetImpl *impl;

??? ????public:????

??????? ????Widget();

??????? ????~Widget();

??????? ????void?Reticulate();

??? };

}

????????然后在Widget.mm中:

#include "Widget.hpp"

#import "ABCWidget.h"

namespace?abc

{

??? struct?WidgetImpl

??? {

??????? ABCWidget *wrapped;

??? };

??? Widget::Widget():impl(newWidgetImpl)

??? {

??????? impl->wrapped = [[ABCWidgetalloc] init];

??? }

??? Widget::~Widget()

??? {

??????? if(impl)

??????????? [impl->wrapped release];

??????? delete?impl;

??? }

??? void?Widget::Reticulate()

??? {

??????? [impl->wrapped reticulate];

??? }

}

????????它的工作原理是——前置聲明。聲明這樣的結構或類對象的指針成員變量、結構或類就足夠了。需要注意的是封裝的對象會在析構函數中釋放。即便對于使用了ARC的項目,我還是建議你對這樣的C++/Objective-C重引用的文件屏蔽掉它。不要讓C++代碼依賴于ARC。在XCode中可以針對個別文件屏蔽掉ARC。Target properties->Build phase頁簽,展開'CompileSources',為特定文件添加編譯選項-fno-objc-arc。


1.2.3 C++中封裝Objective-C類的捷徑

????????您可能已經注意到,PIMPL解決方案使用兩個級別的間接引用。如果包裝的目標類像本例中的一樣簡單,就可能會增大了復雜性。雖然Objective-C的類型一般不能使用在純C++中,不過有一些在C中實際已經定義了。id類型就是其中之一,它的聲明在頭文件中。雖然會失去一些Objective-C的安全性,你還是可以把你的對象直接傳到C++類中:

#include <objc/objc-runtime.h>

namespace?abc

{

??? class?Widget

??? {

??????? id/*ABCWidget* */wrapped;

??????? public:

??????????? Widget();

??????????? ~Widget();

??????????? void?Reticulate();

??? };

}

????????不建議向id對象直接發(fā)送消息。這樣你會失去很多編譯器的檢查機制,特別是對于不同類中有著相同selector名字的不同方法時。所以:

#include "Widget.hpp"

#import "ABCWidget.h"

namespace?abc

{

??? Widget::Widget() : wrapped([[ABCWidgetalloc] init])

??? {

??? }

??? Widget::~Widget()

??? {

??????? [(ABCWidget*)impl release];

??? }

??? void?Widget::Reticulate()

??? {

??????? [(ABCWidget*)impl reticulate];

??? }

}

????????像這樣的類型轉換很容易在代碼中隱藏錯誤,再嘗試一個更好的方式。在頭文件中:

#ifdef __OBJC__

@class?ABCWidget;

#else

typedef struct?objc_object ABCWidget;

#endif


namespace?abc

{

??? class?Widget

??? {

??????? ABCWidget *wrapped;

??????? public:

??????????? Widget();

??????????? ~Widget();

??????????? void?Reticulate();

??? };

}

????????如果這個頭文件被一個mm文件引用,編譯器可以充分識別到正確的類。

????????如果是在純C++模式中引用,ABCWidget*是一個等價的id類型:定義為typedef struct objc_object* id;。

????????#ifdef塊還可以被進一步放到一個可重用的宏中:

#ifdef __OBJC__

#define OBJC_CLASS(name) @class

name

#else

#define OBJC_CLASS(name) typedef

struct objc_object name

#endif

????????現在,我們可以前置聲明在頭文件中一行就可以適用于所有4種語言:

OBJC_CLASS(ABCWidget);


1.3 C++詞匯歧義和沖突

????????OC頭文件中定義了一些標識符,所有的OC程序必須包含的,這些標識符識id,Class,SEL,IMP和BOOL。

????????OC方法內,編譯器預聲明了標識符self和super,就像C++中的關鍵字this。跟C++的this不同的是,self和super是上下文相關的;OC方法外他們還可以用于普通標識符。

????????協(xié)議內方法的參數列表,有5個上下文相關的關鍵字(oneway,in,out,inout,bycopy)。這些在其他內容中不是關鍵字。

????????從OC程序員的角度來看,C++增加了不少新的關鍵字。你仍然可以使用C++的關鍵字做OC selector的一部分,所以影響并不嚴重,但你不能使用他們命名OC類和實例變量。例如,盡管class是C++的關鍵字,但是你仍然能夠使用NSObject的方法class:

[foo class];// OK

????????然而,因為它是一個關鍵字,你不能用class做變量名稱:

NSObject *class; // Error

????????OC里類名和分類名有單獨的命名空間。@interface foo和@interface(foo)能夠同時存在在一個源代碼中。OC++里,你也能用C++中的類名或結構名來命名你的分類。

????????協(xié)議和template標識符使用語法相同但目的不同:

id<someProtocolName> foo;

TemplateType<SomeTypeName> bar;

????????為了避免這種含糊之處,編譯器不允許把id做template名稱。

????????最后,C++有一個語法歧義,當一個label后面跟了一個表達式表示一個全局名稱時,就像下面:

label:::global_name = 3;

????????第一個冒號后面需要空格。OC++有類似情況,也需要一個空格:

receiver selector: ::global_c++_name;


1.4 限制

????????OC++沒有為OC類增加C++的功能,也沒有為C++類增加OC的功能。例如,你不能用OC語法調用C++對象,也不能為OC對象增加構造函數和析構函數,也不能將this和self互相替換使用。類的體系結構是獨立的。C++類不能繼承OC類,OC類也不能繼承C++類。另外,多語言異常處理是不支持的。也就是說,一個OC拋出的異常不能被C++代碼捕獲,反過來C++代碼拋出的異常不能被OC代碼捕獲。

摘自:http://ocen.iteye.com/blog/522028


2 參考鏈接

混合使用Objective-C,C++和Objective-C++

http://blog.csdn.net/horkychen/article/details/7935910

objective-c和c++混合編程

http://www.cnblogs.com/85538649/archive/2011/09/29/2195332.html

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容