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

微信扫一扫,分享到朋友圈

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

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

作者:守望先生

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库

这个时候,版本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++/数据结构与算法/计算机基础/工具等原创技术文章。 后台免费获取经典电子书和视频资源

微信扫一扫,分享到朋友圈

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

amazeui页面校验功能的实现代码

上一篇

jackson、fastjson、kryo、protostuff等序列化工具性能对比

下一篇

你也可能喜欢

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

长按储存图像,分享给朋友