From Swift To C++
From Swift To C++
Objective-C%E5%88%B0Swift/Swift%E5%AD%A6%E4%B9%A0%EF%BC%9A%E4%BB%8EObjective-C%E5%88%B0Swift.md" rel="nofollow,noindex" target="_blank">姊妹篇: 從 Objective-C 到 Swift
C++ 除了在作業系統核心,編譯器等高效能領域中發揮著關鍵作用,同時也是跨平臺開發中不可或缺的角色。值得對底層優化和跨平臺開發有興趣的同學瞭解與學習。
Swift 和 C++ 初看起來是兩種差異較大的語言。但是隨著逐步深入瞭解,我們會發現他們有一個最大的共同點,那就是多正規化程式設計。
這篇文章就按照程式設計正規化(programming paradigm)來組織脈絡(非嚴格劃分,事實上不同程式設計正規化都會用到許多相同的語法特性),下面就讓我們從一個客戶端工程師的角度來品味和對比這兩門語言。
目錄:
-
- 控制流
- 函式
-
- 封裝
- 繼承
- 多型
- 訪問控制
- 訊息傳遞機制
-
- 強型別,靜態型別
- 型別推導
- 值語義 引用語義
- 型別轉換
-
- 智慧指標
- Optional
-
- 實現模型
- 泛型函式
- 泛型型別
- 型別約束
-
- 閉包
面向過程(Procedure Oriented Programming)
控制流(Control Flow )
Selection:
C++ 擁有if
switch
語法。
而Swift除了擁有if
和else
, 還有gurad
用於提前返回。
Loop:
C++ 擁有do while
while
for(::)
range-based for
。
而Swift則擁有repeat while
while
和for in
.
值得一提的是,
Swift從 3.0 版本已經去掉了 C-Style 的for
迴圈
而 C++11 加入的range-based for
和 Swift 的for in
基本是異曲同工的。
std::map<string, int> testMap; for (auto& item : testMap) { cout << item.first << ":" << item.second << endl; }
vat map: Dictionary<String:Int>; for (key,value) in map { }
另外 c++ 的switch
能力也比較弱,只能夠對整型,列舉或一個能隱式轉換為整型或列舉型別的class進行判斷. 而 Swift 的switch
能力強大得多,基本能夠判斷所有型別,包括字串,浮點數等等。
函式
C++ 的函式定義語法和 C 是一脈相承的。
C++ 和 Swift 一樣,也支援指定引數的預設值(Default Parameter Values):
int sum(int a, int b=20) { int result; result = a + b; return result; }
不過 C++ 因為缺少像 Swift 的Function Argument Labels
, 因此定義方法需要遵循:若給某一引數設定了預設值,那麼在引數表中其後所有的引數都必須也設定預設值
呼叫時需要:若給已經設定預設值的引數傳遞實際值,則在引數表中被取代引數的左邊所定義的所有引數,無論是否有預設值,都必須傳遞實際引數。
void func(int a=1,int b,int c=3, int d=4); //error void func(int a, int b=2,int c=3,int d=4); //ok //不能直接選擇給 a 和 c 賦值 func(2,15,20);
而 Swift 則可以靈活地選擇給哪個引數賦值:
func test(a :Int = 1, b:Int, c:Int = 3, d:Int = 4 ) {} test(b:3, d: 5);
C++ 的函式初看沒有什麼可以挖掘的地方,但是當涉及面向物件,模板(泛型程式設計)時,就會有更多強大的語法特性顯現出來。讓我們繼續耐心閱讀下去。
參考連結
面向物件 (Object Oriented Programming)
封裝
-
定義class:
class Person { public: int x; void foo(); private: float z; void bar(); }
-
建構函式和解構函式(Constructors and Destructors)
相當於Swift的Initialization和Deinitialization。
class Foo { private: int x; public: Foo() : x(0) { } Foo(int x) : x(x) { } //上下兩個構造方法是等價的。 Foo(int x) { this->x = x; } };
注意到C++有個特殊構造方法語法叫
Initialization Lists
,用於初始化成員變數。 -
變數初始化語法:
//小括號初始化 string str("hello"); //賦值初始化 string str = "hello"; //花括號初始化c++11後 都推薦這種初始化方法 uniform initialization vector<int> vec = {1,2,3}; //c++ 11 獨有的
C++和Swift有個很大的區別,那就 C++ 如果定義變數時沒有指定初值,則變數被預設初始化(default initialized),也就是會被自動賦值。個人覺得這不是一個好的語言特性, 最好還是像 Swift 一樣強制需要顯式初始化,這樣程式碼不隱晦,一看就知道真正的初始化值。
繼承
//Swift class Person {} class Employee: Person {}
//C++ class Person { }; class Employee : public Person { };
注意到 C++ 多了一個public
關鍵詞,這代表了 Person 類的 public members 在Employee 中還是 public 的。如果將public
替換成private
,則外界呼叫 Employee 時,父類 Person 的 public members 是不可見的。
C++ 支援多繼承。而 Swift 則可以通過 Protocol 和 Protocol Extension 來實現類似多繼承的特性。
多型(Polymorphism)
我們來看看下面這段程式碼:
class Foo { public: int value() { return 5; } }; class Bar : public Foo { public: int value() { return 10; } }; Bar *b = new Bar(); Foo *f = (Foo*)b; printf(“%i”, f->value()); // Output = 5
我們驚奇地發現,返回值是5,這和 Swift 的行為是不同的。這在 C++ 叫做 Static Binding。方法的呼叫在編譯期就確定了。我們需要利用 C++ 的語法特性virtual function
來實現多型。讓方法的呼叫在執行時確定(dynamic binding)。
class Foo { public: virtual int value() { return 5; } }; class Bar : public Foo { public: virtual int value() override { return 10; } }; Bar *b = new Bar(); Foo *f = (Foo*)b; printf(“%i”, f->value()); // Output = 10
類似於 Swift 的 Protocol。在C++中,我們是通過 pure virtual function (or abstract function)來定義介面的。
class Base { int x; public: //pure virtual function virtual void fun() = 0; int getX() { return x; } }; // This class ingerits from Base and implements fun() class Derived: public Base { int y; public: //c++11 用override顯式表示過載虛擬函式 virtual void fun() override { cout << "fun() called"; } };
訪問控制(Access Control):
c++也有訪問控制,分為public
,private
,protected
,friend
。
區別在於Swift是沒有protected
的,protected
指子類也能訪問和修改。
friend
是一個比較特別的控制語法,大多數程式語言都沒有這個特性,C++中的友元機制允許類的非公有成員被一個類或者函式訪問。
值得注意的是:
-
在C++中,class 的預設訪問控制是
private
,而 struct 或 union 則是public
。 -
繼承也需要指定訪問控制。
``c++ class Derived : Base //上下是等價的。 class Derived : private Base ``` 如果不指定 public, 我們是無法在子類中使用父類的方法的。
參考連結:
訊息傳遞機制 (Method Dispatch/Message passing)
C++ 和 Swift 都有 static dispatch 和 dynamic dispatch 兩種訊息傳遞機制。
對於前者,Swift 和 C++ 都是編譯時(compilation time)就確定了呼叫地址。
對於後者,Swift 的 dynamic dispatch 有兩種形式,一種是通過Objective-C的runtime進行分發。一種是通過和C++類似的vtable
進行分發。
除了標記為final
,private
,@objc dynamic
的方法。Swift的方法都類似C++標記了vitrual
的方法。
How does iOS Swift Runtime work
Dynamic Dispatch in Object Oriented Languages
型別系統(Type System)
強型別,靜態型別
和Swift一樣,C++也是一個強型別(strongly typed),靜態型別(statically typed)的程式語言。
從語法的角度來看:
強型別:一旦變數被指定某個資料型別,如果不經轉換,即永遠是此資料型別。
靜態型別:變數型別在編譯時就確定.
靜態型別又可以細分為:manifest type
和type-inferred
語言。manifest type
需要我們顯式指定變數的型別,如:int a。 而type-inferred
則可以由編譯器來幫我們推導型別。
型別推導 (Type Inference)
C++ 和 Swift 都具備型別推導的能力。
//c++ auto a = 1;
//swift let a = 1
C++ 的型別推導能力還能用在函式的返回型別中:
//c++14起支援 template<class T, class U> auto add(T t, U u) { return t + u; } //the return type is the type of operator+(T, U)
值語義 引用語義 (value semantics and reference semantics)
C++ 不像 Swift 將型別明確分為 Reference Type 和 Value Type 。而是通過指標和引用來實現引用語義。在C++中,classes 預設是 value types.
class Foo { public: int x; }; void changeValue(Foo foo) { foo.x = 5; } Foo foo; foo.x = 1; changeValue(foo); // foo.x still equals 1
需要指定pass a variable “by reference”.
void changeValue(Foo &foo) { foo.x = 5; } Foo foo; foo.x = 1; changeValue(foo); // foo.x equals 5
型別轉換 ( Type Conversions )
-
隱式型別轉換 ( Implicit type conversions )
Swift是沒有隱式型別轉換的,而C++有。
//成立 float a = 1, double b = a; //精度損失,會有warning double a = 1, float b = a;
個人不是很贊同隱式型別轉換,我認為一個強型別語言的所有型別轉換都應該是顯式的。這樣更統一和規範,也許隱式型別轉換能夠帶來一點編寫程式碼的便利性,但也隱藏了問題,特別是有精度損失的隱式轉換。也許最好的做法是保留隱式型別轉換,但是隻允許
Widening conversions
,也即提高精度的轉換。 -
顯示型別轉換 ( Explicit conversions )
現代C++通過
static_cast
,dynamic_cast
,const_cast
來進行顯式型別轉換。dynamic_cast
就類似Swift的as?
, 是安全的型別轉換操作。Base* b = new Base(); // Run-time check to determine whether b is actually a Derived* Derived* d3 = dynamic_cast<Derived*>(b); // If b was originally a Derived*, then d3 is a valid pointer. if(d3) { d3->DoSomethingMore(); }
參考連結:
記憶體管理
智慧指標
myPerson = nullptr; myPerson->doSomething(); // crash!
在傳統 C++裡,一般用new
和delete
這兩個語法進行記憶體管理,稍有不慎就會導致記憶體洩露等問題。
好在C++11也引入了引用計數進行記憶體管理。具體的語法關鍵詞是使用std::shared_ptr
,std::weak_ptr
,unique_ptr
。
unique_ptr
: 只能有一個unique指標指向記憶體, 不存在多個unique指標指向同塊記憶體
unique_ptr<T> myPtr(new T);// Okay unique_ptr<T> myOtherPtr = myPtr; // Error: Can't copy unique_ptr
shared_ptr
// 初始化 shared_ptr<int> y = make_shared<int>(); shared_ptr<Resource> obj = make_shared<Resource>(arg1, arg2); // arg1, arg2是Resource建構函式的引數
weak_ptr
C++中提供了lock函式來實現該功能。如果物件存在,lock()函式返回一個指向共享物件的shared_ptr
,否則返回一個空shared_ptr
。
auto sharedPtr = make_shared<XXXDelegate>(); auto delegate =weak_ptr<XXXDelegate>(sharedPtr); if (auto unwrap = delegate->lock()) { unwrap->XXX(); }
C++ 開發者在使用智慧指標的過程中總結出四句原則:
-
用
shared_ptr
,不用new
-
使用
weak_ptr
來打破迴圈引用 -
用
make_shared
來生成shared_ptr
-
繼承
enable_shared_from_this
來使一個類能獲取自身的shared_ptr
在自己實際開發的過程中,還總結出一個:如果一個物件在整個生命週期中會需要被智慧指標所管理,那麼一開始就要用智慧指標管理。
參考連結:
Optional:
optional<int> o = str2int(s); // 'o' may or may not contain an int if (o) {// does optional contain a value? return *o;// use the value }
c++的判斷不是編譯期強制的。沒有像Swift一樣的 unwrap 語法。還是需要自己判空,和指標判空類似,但是 Optional 的優點是能夠表達一個非指標的物件是否為空。
泛型程式設計
C++ 有著比 Swift 更強大的泛型程式設計能力,但是代價就是語法和程式碼會更加晦澀。
實現模型(Implementation Model)
實際上 Swift 的泛型和 C++ 的泛型的實現模型有著本質區別。C++ 的泛型(模板)是在編譯期生成每個型別具體的實現。而 Swift 則是利用型別資訊和 Swift runtime來實現。 這個話題非常巨集大艱深,涉及到編譯器的底層細節,有興趣的讀者可以加以研究並分享。
在這裡我們簡單通過一個泛型函式來簡單感受一下:
template <typename T> T f(T t) { T copy = t; return copy; } f(1); f(1.2);
c++ 的編譯時會生成兩份程式碼:
int f(int t) { int copy = t; return copy; } float f(float t) { float copy = t; return copy; }
而 Swift:
func f<T>(_ t: T) ->T { let copy = t return copy }
編譯器實現則類似以下,不會為每個型別生成單獨一份實現。
void f(opaque *result,opaque *result,type *T) { //vwt: value witness table //利用型別資訊來實現 T->vwt->XXX(X); }
泛型函式
Swift:
func genericSwap<T>(inout a:T,inout _ b:T){ let temp = a a = b b = temp }
C++:
template <typename T> void swap(T &a, T &b) { T temp = a; a = b; b = temp; }
泛型型別
template <typename T> class Triplet { private: T a, b, c; public: Triplet(T a, T b, T c) : a(a), b(b), c(c) {} const T& getA() { return a; } const T& getB() { return b; } const T& getC() { return c; } }; Triplet<int> intTriplet(1, 2, 3); Triplet<float> floatTriplet(3.141, 2.901, 10.5);
c++通過在方法或型別前面定義template <typename T>
,來定義型別引數(type parameter
)
在Swift中泛型型別我們需要通過 typealias 暴露型別引數給外面。 在C++中,需要用typedef暴露給外面使用。
template <typename Reqest,typename Response> class kindaBaseCgi { public: typedef Reqest RequestType; typedef Response ResponseType; }
型別約束
Swift有型別約束(Type Constraints
)來約束型別引數繼承某個類或遵循某個協議。
目前C++中,沒有特別的語法來實現這個效果。我們需要藉助static_assert
在編譯中檢查型別:
template<typename T> class YourClass { YourClass() { // Compile-time check static_assert(std::is_base_of<BaseClass, T>::value, "type parameter of this class must derive from BaseClass"); } }
在編寫 C++ 模板程式碼的過程中,其實我覺得型別約束這個的重要性並不大。這歸結於我們上面提到的語言對泛型的實現模式的不同。
考慮到如下的模板函式:
template <typename T> T test(T a, T b) { return a + b; } test(1,2); //編譯的時候,會為 int 生成實現 int test(int a, int b) { return a + b; } //而這個例項化(instantiation)後的函式是正確的,所以編譯成功
而在 Swift 中:
//編譯報錯:Binary operator '+' cannot be applied to two 'T' operands func test<T>(a:T, b:T) -> T { return a + b }
Swift 因為需要靠型別資訊和 runtime 來實現泛型。因此它需要知道 T 這個型別能夠進行+
操作。
//編譯成功 func test<T:Numeric>(a:T, b:T) -> T { return a + b }
C++ 編譯成功與否決定於模板例項化的時候。這一特性非常強大,我們不需要一開始就提供所有信息給編譯器。直到例項化時,編譯器才會檢查實現是否正確,是否該型別能夠支援+
操作。
C++ 還有一個Swift沒有的強大特性,那就是SFINEA
。 同時也引入了一個新的程式設計正規化。那就是Compile-time Programming
,這裡暫不展開。
參考連結:
函數語言程式設計
C++11 後,引入了閉包概念,使得 C++ 的函數語言程式設計變得更簡單清晰。
閉包(Closures)
在C++, 閉包被稱為lambda
.
auto y = [] (int first, int second) { return first + second; }; //顯式宣告 function<int(int, int)> f2 = [](int x, int y) { return x + y; };
一個簡單的閉包語法:
[ captures ] ( params ) -> ret { body }
- Capturing Values:
在Swift中,閉包能夠自動幫助我們捕獲values。
let i = 10 let closure = { print(i) } closure()
而c++需要開發者顯式指定捕獲變數。
auto i = 1; auto closure = [] { std::cout << i << std::endl;}; //error: 'i' is not captured
auto i = 1; auto closure = [=] { std::cout << i << std::endl;}; closure(); //編譯成功
參考連結:
C++函式指標、函式物件與C++11 function物件對比分析
併發程式設計( Concurrency Programming)
Swift 目前沒有語言級別的併發程式設計機制,在業務開發中,我們通常通過Grand Central Dispatch(GCD)
進行併發程式設計,在此不再贅述。
從 C++11 開始,C++ 標準第一次承認多執行緒在語言中的存在,並在標準庫中為多執行緒提供了元件。
我們可以通過這樣的一個例子感受一下:
//Calling std::async with lambda function std::future<std::string> resultFromDB = std::async([](std::string recvdData){ std::this_thread::sleep_for (seconds(5)); //Do stuff like creating DB Connection and fetching Data return "DB_" + recvdData; }, "Data");
但是深入使用,我們會非常 C++ 的併發程式設計使用起來會非常晦澀。比如並沒有一個原生的執行緒池機制來保證效能和健壯性。也沒有一個直觀的機制在其他執行緒回撥主執行緒,這在GCD
中,只需要我們 disptach 到 mainQueue 即可。
針對客戶端的跨平臺開發,如果涉及到非同步,Timer的時候,建議還是可以利用各個平臺的上層元件。C++ 層負責定義介面,上層負責實現。
參考連結: