探索C++与Go的接口底层实现

1、C++ 中的“接口”

C++并没有明确的“接口”,一般约定继承某个类,已达到接口的“实现”。

首先我们来看下单继承的内存布局(依赖各厂商的实际实现,这里仅以微软实现为例进行说明····感谢宇宙最强IDE····)

其多态主要由虚函数表(vfptr)实现 : 指针或引用调用虚函数时,在运行时由对象的虚函数表+函数声明顺序决定绑定到哪个函数上

class IDuck {
public:          
    //嘎嘎地叫    
    virtual void GaGaSpeaking() = 0;      
       
    //老爷的官步    
    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;
}

内存布局为:

强制调用成员函数(甚至可以是私有)

多继承

多继承下其实也是类似,按继承顺序依次排列,还是看代码示例
class IDuck {
public:          
    //嘎嘎地叫    
    virtual void GaGaSpeaking() = 0;         
    //老爷的官步    
    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;
}

内存布局为:

菱形继承(略)

更多请参考

2、Go中的接口

Golang将interface作为一种类型,并且不依赖继承,而是以一种类似于duck-typing的实现。所谓duck-typing,是一种动态类型风格:当一个obj走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么它就可以被称为鸭子。

既然Go并没有像C++那样要求主动告诉编译器需要继承哪个父类,那么是如何实现动态类型的呢?(基于Go1.6,1.7及之后版本由于nameOff不方便gdb打印)

首先,interface由两部分组成{tab, data},其中tab保存了接口的元数据,这个很重要。

type iface struct {
        tab  *itab
        data unsafe.Pointer
}

itab中比较重要的有interfacetype及fun[],其中interfacetype保存了该接口需要实现哪些方法,fun[]则保存动态类型是如何实现这些方法的

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
}

附:一篇经典论文中的图解


research!rsc: Go Data Structures: Interfaces

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调试一下
首先,看下结构类型与两个接口的内存关系

(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}

以及其动态类型的具体实现(可以看到,都指向了tanglaoya的具体实现)

(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}

及其动态类型的具体实现

(gdb) info symbol actor.tab.fun[0]
main.(*DonaldDuck).MakeFun in section .text of /home/yugui/go/go

3、总结

C++在代码编写时就明确了是否实现某个接口,并将接口信息附加在自己的内存中,但is-A的模式越来越限制模块间的解耦;Golang其宽松的接口充分降低了耦合的发生,但可能在代码书写无意中却实现了某个接口.. 此外,其实现可能会比较绕,容易发生其他错误(比如经典的interface与nil的比较等等)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 书名:《代码的未来》作者:松本行弘(Yukihiro Matsumoto)译者:**周自恒 **本试读章节摘自:『...
    Shyujikou阅读 1,355评论 0 10
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,036评论 29 470
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,293评论 18 399
  • 博弈:赌上,博观战: 赌艳羡着别人那份洒脱, 内心叫嚣着, 那份唾手可得在眼前招手, 手捅窗纸般易破, 脑已热,烟...
    懂我笑容任阅读 459评论 1 0