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

语鬼_7022 · · 951 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

1、C++ 中的“接口”

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

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

其多态主要由虚函数表(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
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调试一下
首先,看下结构类型与两个接口的内存关系



可见,duck与actor的data指针都指向dd
然后是Duck接口的方法集:



以及其动态类型的具体实现:




可以看到,都指向了tanglaoya的具体实现

再看看Actor的方法集:




及其动态类型的具体实现



3、总结

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


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:语鬼_7022

查看原文:探索C++与Go的接口底层实现

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

951 次点击  
加入收藏 微博
下一篇:golang-字符串
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传