16

API这样设计?等着程序挂掉吧!

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzI2OTA3NTk3Ng%3D%3D&%3Bmid=2649286386&%3Bidx=1&%3Bsn=16a5b5d12dd3866c20b71d797598bf44
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

来源:公众号【编程珠玑】

作者:守望先生

ID:shouwangxiansheng

之前在《 PIMPL-隐藏类的私有成员 》中介绍了一种隐藏类的私有成员的方法,或者说隐藏接口实现细节的方法-PIMPL。

假设提供的接口的入参比较复杂,可能有人

会考虑使用结构体作为入参。当你考虑这么做的时候,灾难也将会随之而来

……

示例:

// 来源:公众号【编程珠玑】
// 作者:守望先生
// api.h
#include<iostream>
struct Param{
    int num;
    std::string str;
};
void TestFun(const Param &param);

// api.cc
#include "api.h"
void TestFun(const Param &param){
    std::cout<<"num:"<<param.num<<" str:"<<param.str.c_str()<<std::endl;
}

假设提供TestFun作为一个对外接口,我们编译并制作为静态库:

$ g++ -c api.cc -I./
$ ar -rcs libapi.a api.o 

关于静态库的制作,请参考《 Linux下如何制作静态库? 》。

另外一个程序main.cc这么使用它:

// 来源:公众号编程珠玑
// 作者:守望先生
#include "api.h"
int main(){
    Param param;
    param.num = 10;
    param.str = "24"; 
    TestFun(param);
    return 0;
}

编译链接使用:

$ g++ -o main main.cc -L./ -lapi -I ./
$ ./main

看起来并没有什么问题,有新的参数,可以直接在Param中增加即可,扩展性也不错。

问题来了

目前来看是没有什么问题的,但是假设,还有另外一个库要使用它,例如:

// 来源:公众号编程珠玑
// 作者:守望先生
// use_api.h
#include"api.h"
void UseApi();

// use_api.cc
#include"use_api.h"
void UseApi(){
    Param param;
    param.num = 10;
    param.str = "24"; 
    TestFun(param);
}

也将它作为静态库:

$ g++ -c use_api.cc -I./
$ ar -rcs libuse_api.a use_api.o 

这个时候同样主程序会用到我们的原始api,但是却使用了不同的版本,比如,新增了Param中新增了一个字段ext:

// 来源:公众号【编程珠玑】
// 作者:守望先生
// api.h
#include<iostream>
struct Param{
    int num;
    std::string str;
    std::string ext;
};
void TestFun(const Param &param);

// api.cc
#include "api.h"
void TestFun(const Param &param){
    std::cout<<"num:"<<param.num<<" str:"<<param.str.c_str()<<" ext:"<<param.ext.c_str()<<std::endl;
}

重新生成静态库:

$ g++ -c api.cc -I./
$ ar -rcs libapi.a api.o 

这个时候,通过use_api使用api接口,但是链接新的库:

// 来源:公众号编程珠玑
// 作者:守望先生
#include "use_api.h"
int main(){
    UseApi();
    return 0;
}

这个时候,再去编译链接,并运行:

$ g++ -o main main.cc -I./ -L./ -luse_api -lapi
$ ./main
Segmentation fault (core dumped)

看到没有,喜闻乐见的core dumped了,分析core还会发现,是由于访问非法地址导致的。

我们再来梳理一下这个过程:

  • 提供库libapi.a版本A

  • libuse_api使用版本A进行编译,使用A版本的头文件

  • libapi.a库升级到B版本,其中头文件中增加了字段,并且实现也引用了新的字段

  • 主程序使用了use_api,但是链接了版本B的libapi.a库

VV3ymyU.png!mobile

这个时候,版本B的实现访问了新的字段,还是use_api中还是使用A版本,并没有传入新字段,因此自然会导致非法访问。

如何解决?

很简单,不直接暴露成员,而是提供setter和getter,而提供方式和前面提到的PIMPL方法类似。

// api.h
// 来源:公众号编程珠玑
// 作者:守望先生
#include<iostream>
#include<memory>
class Param{
public:
    void SetNum(int num);
    int GetNum() const;
    void SetStr(const std::string &str);
    std::string GetStr() const;
    void SetExt(const std::string &str);
    std::string GetExt() const;
    Param();
  private:
    class ParamImpl;
    std::unique_ptr<ParamImpl> param_impl_;
};
void TestFun(const Param &param);

在这里头文件中只提供setter和getter,而完全不暴露成员,具体成员的设置在ParamImpl中实现:

// api.cc
// 来源:公众号编程珠玑
// 作者:守望先生
#include "api.h"
class Param::ParamImpl{
  public:
    int num;
    std::string str;
    std::string ext;
};
Param::Param(){
    param_impl_.reset(new ParamImpl);
}
// 析构函数必须要
Param::~Param() = default;
void Param::SetNum(int num){
    param_impl_->num = num;
}
int Param::GetNum() const {
    return  param_impl_->num;
}
void Param::SetStr(const std::string &str){
    param_impl_->str = str;
}
void Param::SetExt(const std::string &ext){
    param_impl_->ext = ext;
}
std::string Param::GetStr() const {
    return param_impl_->str;
}
std::string Param::GetExt() const {
    return param_impl_->ext;
}
void TestFun(const Param &param){
    std::cout<<"num:"<<param.GetNum()<<" str:"<<param.GetStr().c_str()<<"ext:"<<param.GetExt().c_str()<<std::endl;
}

通过上面的方式,不会直接暴露成员函数,而是提供接口设置或者获取,而在实现中,即便出现新的版本增加了接口,最多也只是获取到默认值,而不会导致程序崩溃。

总结

本文和之前的文章实现方法是一样的,这样不暴露成员的做法,更大程度避免了链接库不一致导致的问题,你学会了吗?

示例代码可通过阅读原文查看。

相关精彩推荐

【经典推荐】双十一了,看看哪些好书值得买

PIMPL:休想窥探我的隐私!

如何制作属于自己的静态库?

const关键字你搞懂了?这个编译问题你可能都搞不明白!

关注公众号【编程珠玑】,获取更多Linux/C/C++/数据结构与算法/计算机基础/工具等原创技术文章。 后台免费获取经典电子书和视频资源

2eM7Zzb.jpg!mobile


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK