1、C++ 中的“接口”
C++并沒有明確的“接口”涩咖,一般約定繼承某個(gè)類海诲,已達(dá)到接口的“實(shí)現(xiàn)”。
首先我們來看下單繼承的內(nèi)存布局(依賴各廠商的實(shí)際實(shí)現(xiàn)檩互,這里僅以微軟實(shí)現(xiàn)為例進(jìn)行說明····感謝宇宙最強(qiáng)IDE····
)
其多態(tài)主要由虛函數(shù)表(vfptr)實(shí)現(xiàn) : 指針或引用調(diào)用虛函數(shù)時(shí)饿肺,在運(yùn)行時(shí)由對象的虛函數(shù)表+函數(shù)聲明順序決定綁定到哪個(gè)函數(shù)上
class IDuck {
public:
//嘎嘎地叫
virtual void GaGaSpeaking() = 0;
//老爺?shù)墓俨?
virtual void OfficialWalking() = 0;
private:
unsigned int height;
};
class DonaldDuck : public IDuck {
public:
void GaGaSpeaking() {
std::cout << "DonaldDuck Speak" << std::endl;
}
void OfficialWalking() {
std::cout << "DonaldDuck Walk" << std::endl;
}
};
int main(int argc, _TCHAR* argv[])
{
DonaldDuck * duck = new DonaldDuck();
duck->GaGaSpeaking();
duck->OfficialWalking();
cout << "---------- hooking --------" << endl;
typedef void(*DuckFunc)();
int * addr = (int*)duck;
DuckFunc f1 = (DuckFunc)(*((int*)(*addr)));
f1();
DuckFunc f2 = (DuckFunc)(*((int*)(*addr)+1));
f2();
return 0;
}
內(nèi)存布局為:
強(qiáng)制調(diào)用成員函數(shù)(甚至可以是私有)
多繼承
多繼承下其實(shí)也是類似,按繼承順序依次排列盾似,還是看代碼示例
class IDuck {
public:
//嘎嘎地叫
virtual void GaGaSpeaking() = 0;
//老爺?shù)墓俨?
virtual void OfficialWalking() = 0;
private:
unsigned int height;
};
class IActor {
public:
//搞笑
virtual void MakeFun() = 0;
private:
std::string Name;
};
class DonaldDuck : public IDuck, public IActor {
public:
void GaGaSpeaking() {
std::cout << "DonaldDuck Speak" << std::endl;
}
void OfficialWalking() {
std::cout << "DonaldDuck Walk" << std::endl;
}
void MakeFun() {
std::cout << "Wa HAHAHA ~~~" << endl;
}
};
int main(int argc, _TCHAR* argv[])
{
DonaldDuck * duck = new DonaldDuck();
duck->GaGaSpeaking();
duck->OfficialWalking();
duck->MakeFun();
cout << "---------- hooking --------" << endl;
typedef void(*DuckFunc)();
int * addr = (int*)duck;
DuckFunc f1 = (DuckFunc)(*((int*)(*addr)));
f1();
DuckFunc f2 = (DuckFunc)(*((int*)(*addr)+1));
f2();
typedef void(*ActorFunc)();
int * addr2 = (int*)(*(int*)((IActor*)duck));
ActorFunc f3 = (ActorFunc)(*(int*)(addr2));
f3();
return 0;
}
內(nèi)存布局為:
菱形繼承(略)
2敬辣、Go中的接口
Golang將interface作為一種類型,并且不依賴?yán)^承零院,而是以一種類似于duck-typing的實(shí)現(xiàn)溉跃。所謂duck-typing,是一種動態(tài)類型風(fēng)格:當(dāng)一個(gè)obj走起來像鴨子告抄、游泳起來像鴨子撰茎、叫起來也像鴨子,那么它就可以被稱為鴨子打洼。
既然Go并沒有像C++那樣要求主動告訴編譯器需要繼承哪個(gè)父類龄糊,那么是如何實(shí)現(xiàn)動態(tài)類型的呢?(基于Go1.6募疮,1.7及之后版本由于nameOff不方便gdb打印)
首先炫惩,interface由兩部分組成{tab, data},其中tab保存了接口的元數(shù)據(jù),這個(gè)很重要阿浓。
type iface struct {
tab *itab
data unsafe.Pointer
}
itab中比較重要的有interfacetype及fun[]他嚷,其中interfacetype保存了該接口需要實(shí)現(xiàn)哪些方法,fun[]則保存動態(tài)類型是如何實(shí)現(xiàn)這些方法的
type itab struct {
inter *interfacetype
_type *_type
link *itab
bad int32
unused int32
fun [1]uintptr // variable sized
}
type interfacetype struct {
typ _type
mhdr []imethod
}
type imethod struct {
name *string
pkgpath *string
_type *_type
}
附:一篇經(jīng)典論文中的圖解
e.g. 唐老鴨的go版本
package main
import (
"fmt"
)
type Duck interface {
GaGaSpeaking()()
OfficialWalking()()
}
type Actor interface {
MakeFun()()
}
type DonaldDuck struct {
height uint
name string
}
func (dd *DonaldDuck) GaGaSpeaking()() {
fmt.Println("DonaldDuck gaga")
}
func (dd *DonaldDuck) OfficialWalking()() {
fmt.Println("DonaldDuck walk")
}
func (dd *DonaldDuck) MakeFun()() {
fmt.Println("DonaldDuck make fun")
}
func main() {
dd := &DonaldDuck{10, "tang lao ya" }
var duck Duck = dd
var actor Actor = dd
duck.GaGaSpeaking()
actor.MakeFun()
dd.OfficialWalking()
}
我們用gdb調(diào)試一下
首先,看下結(jié)構(gòu)類型與兩個(gè)接口的內(nèi)存關(guān)系
(gdb) p dd
$1 = (struct main.DonaldDuck *) 0xc82000e180
(gdb) p duck
$2 = {tab = 0x7ffff7f721c0, data = 0xc82000e180}
(gdb) p actor
$3 = {tab = 0x7ffff7f721f0, data = 0xc82000e180}
可見筋蓖,duck與actor的data指針都指向dd
然后是Duck接口的方法集:
(gdb) p duck.tab.inter.mhdr.len
$4 = 2
(gdb) p *(duck.tab.inter.mhdr.array[0].name)
$5 = {str = 0x4ff8c0 "GaGaSpeaking", len = 12}
(gdb) p *(duck.tab.inter.mhdr.array[1].name)
$6 = {str = 0x4ffb30 "OfficialWalking", len = 15}
以及其動態(tài)類型的具體實(shí)現(xiàn)(可以看到卸耘,都指向了tanglaoya的具體實(shí)現(xiàn))
(gdb) info symbol duck.tab.fun[0]
main.(*DonaldDuck).GaGaSpeaking in section .text of /home/yugui/go/go
(gdb) info symbol duck.tab.fun[1]
main.(*DonaldDuck).OfficialWalking in section .text of /home/yugui/go/go
再看看Actor的方法集:
(gdb) p actor.tab.inter.mhdr.len
$12 = 1
(gdb) p *(actor.tab.inter.mhdr.array[0].name)
$13 = {str = 0x4fcc10 "MakeFun", len = 7}
及其動態(tài)類型的具體實(shí)現(xiàn)
(gdb) info symbol actor.tab.fun[0]
main.(*DonaldDuck).MakeFun in section .text of /home/yugui/go/go
3、總結(jié)
C++在代碼編寫時(shí)就明確了是否實(shí)現(xiàn)某個(gè)接口粘咖,并將接口信息附加在自己的內(nèi)存中蚣抗,但is-A的模式越來越限制模塊間的解耦;Golang其寬松的接口充分降低了耦合的發(fā)生瓮下,但可能在代碼書寫無意中卻實(shí)現(xiàn)了某個(gè)接口.. 此外翰铡,其實(shí)現(xiàn)可能會比較繞,容易發(fā)生其他錯(cuò)誤(比如經(jīng)典的interface與nil的比較等等)