c – 在库的公共API中转换远离std :: string,std :: ostream等

c – 在库的公共API中转换远离std :: string,std :: ostream等,第1张

概述对于具有相同二进制文件的许多工具链中的API / ABI兼容性,它是 well known that STL容器,std :: string和其他标准库类(如iostream)在公共头文件中是禁止的. (例外情况是,如果为每个受支持的工具链版本分配一个构建;一个为最终用户编译提供没有二进制文件的源,在本例中不是首选选项;或者一个转换为其他一些内联容器,以便不同的std实现不会被库提取.) 如果已经 对于具有相同二进制文件的许多工具链中的API / ABI兼容性,它是 well known that STL容器,std :: string和其他标准库类(如iostream)在公共头文件中是禁止的. (例外情况是,如果为每个受支持的工具链版本分配一个构建;一个为最终用户编译提供没有二进制文件的源,在本例中不是首选选项;或者一个转换为其他一些内联容器,以便不同的std实现不会被库提取.)

如果已经有一个已发布的库API没有遵循此规则(请求朋友),那么最好的路径是什么,同时保持尽可能多的向后兼容性,并且我不能支持编译时断点?我需要支持windows和linux.

重新考虑我正在寻找的ABI兼容性水平:我不需要它是疯狂的未来证明.我主要是为每个版本的多个流行的linux发行版只做一个库二进制文件. (目前,我为每个编译器发布一个,有时为特殊发行版(RHEL vs Debian)发布特殊版本.与MSVC版本相同的问题 – 所有支持的MSVC版本的一个DLL将是理想的.)其次,如果我不’在破解修复版本中打破API,我希望它与ABI兼容,并且无需重建客户端应用程序即可替换掉DLL / SO.

我有三个案例,有一些初步的建议,模仿Qt到一定程度.

旧的公共API:

// Case 1: Non-virtual functions with containersvoID Foo( const char* );voID Foo( const std::string& );// Case 2: Virtual functionsclass bar{public:    virtual ~bar() = default;    virtual voID VirtFn( const std::string& );};// Case 3: Serializationstd::ostream& operator << ( std::ostream& os,const bar& bar );

案例1:带容器的非虚函数

从理论上讲,我们可以将std :: string用法转换为非常类似于std :: string_vIEw的类,但是在我们库的API / ABI控件下.它将在我们的库头中从std :: string转换,以便编译的库仍然接受但是独立于std :: string实现并且向后兼容:

新API:

class MyStringVIEw{public:    MyStringVIEw( const std::string& ) // Implicit and inline    {        // Convert,possibly copying    }    MyStringVIEw( const char* ); // Implicit    // ...   };voID Foo( MyStringVIEw ); // Ok! Mostly backwards compatible

大多数客户端代码没有做出异常的事情,比如获取Foo的地址,无需修改即可运行.同样,我们可以创建自己的std :: vector替换,但在某些情况下可能会导致复制惩罚.

Abseil’s ToW #1建议从util代码开始,然后进行处理,而不是从API开始.还有其他任何提示或陷阱吗?

案例2:虚拟功能

但虚拟功能怎么样?如果我们更改签名,我们会破坏向后兼容性.我想我们可以让旧的一个到位,最后强制破坏:

// Introduce base class for functions that need to be finalclass barBase{public:    virtual ~barBase() = default;    virtual voID VirtFn( const std::string& ) = 0;};class bar : public barBase{public:    voID VirtFn( const std::string& str ) final    {        VirtFn( MyStringVIEw( str ) );    }    // Add new overload,also virtual    virtual voID VirtFn( MyStringVIEw );};

现在,旧的虚函数的覆盖将在编译时中断,但是使用std :: string的调用将自动转换.覆盖应该使用新版本,并在编译时中断.

这里有任何提示或陷阱吗?

案例3:序列化

我不知道如何处理iostreams.一种选择,存在一些效率低下的风险,是将它们内联定义并通过字符串重新路由它们:

MyString ToString( const bar& ); // I control this,Could be a virtual function in bar if needed// Here I publicly interact with a std object,so it must be inline in the headerinline std::ostream& operator << ( std::ostream& os,const bar& bar ){    return os << ToString( bar );}

如果我将ToString()作为一个虚函数,那么我可以迭代所有bar对象并调用用户的覆盖,因为它只依赖于MyString对象,这些对象在头部定义,它们与std对象(如流)交互.

想法,陷阱?

解决方法 第1层

使用良好的字符串视图.

不要使用std :: string const&虚拟过载;没有理由.无论如何,你正打破ABI.一旦他们重新编译,他们将看到新的基于字符串视图的重载,除非他们正在获取并存储指向虚函数的指针.

要在不使用中间字符串的情况下进行流式传输,请使

voID cps_to_string( bar const& bar,MyFunctionVIEw< voID( MyStringVIEw ) > cps );

其中cps被重复调用部分缓冲区,直到对象被序列化出来.写<<最重要的是(在标题中内联).函数指针间接有一些不可避免的开销. 现在只使用虚拟接口,永远不会重载虚方法,并始终在vtable的末尾添加新方法.因此,不要暴露复杂的heirarchIEs.扩展vtable是ABI安全的;加到中间不是. FunctionVIEw是一个简单的手动非拥有std函数克隆,其状态为voID *和R(*)(voID *,args&& …),它们应该是ABI稳定的以跨越库边界.

template<class Sig>struct FunctionVIEw;template<class R,class...Args>struct FunctionVIEw<R(Args...)> {  FunctionVIEw()=default;  FunctionVIEw(FunctionVIEw const&)=default;  FunctionVIEw& operator=(FunctionVIEw const&)=default;  template<class F,std::enable_if_t<!std::is_same< std::decay_t<F>,FunctionVIEw >{},bool> = true,std::enable_if_t<std::is_convertible< std::result_of_t<F&(Args&&...)>,R>,bool> = true  >  FunctionVIEw( F&& f ):    ptr( std::addressof(f) ),f( [](voID* ptr,Args&&...args)->R {      return (*static_cast< std::remove_reference_t<F>* >(ptr))(std::forward<Args>(args)...);    } )  {}private:  voID* ptr = 0;  R(*f)(voID*,Args&&...args) = 0;};template<class...Args>struct FunctionVIEw<voID(Args...)> {  FunctionVIEw()=default;  FunctionVIEw(FunctionVIEw const&)=default;  FunctionVIEw& operator=(FunctionVIEw const&)=default;  template<class F,Args&&...args)->voID {      (*static_cast< std::remove_reference_t<F>* >(ptr))(std::forward<Args>(args)...);    } )  {}private:  voID* ptr = 0;  voID(*f)(voID*,Args&&...args) = 0;};

这使您可以通过API障碍传递通用回调.

// f can be called more than once,be prepared:voID ToString_cps( bar const& bar,FunctionVIEw< voID(MyStringVIEw) > f );inline std::ostream& operator<<( std::ostream& os,const bar& bar ){  ToString_cps( bar,[&](MyStringVIEw str) {    return os << str;  });  return os;}

并实施ostream& << MyStringVIEw const&在标题中.

2级

将每个 *** 作从头文件中的C API转发到extern“C”pure-C函数(即将StringVIEw作为一对char const * ptrs传递).仅导出extern“C”符号集.现在,符号改变不再破坏ypur ABI.

C ABI比C更稳定,并且通过强制您将库调用分解为“C”调用,可以使ABI明显改变.使用C头胶使物品清洁,C使ABI坚固.

如果您愿意冒险,可以保留纯虚拟接口;使用与上面相同的规则(简单的heirarchIEs,没有重载,只添加到最后),你将获得体面的ABI稳定性.

总结

以上是内存溢出为你收集整理的c – 在库的公共API中转换远离std :: string,std :: ostream等全部内容,希望文章能够帮你解决c – 在库的公共API中转换远离std :: string,std :: ostream等所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/1238619.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-06
下一篇 2022-06-06

发表评论

登录后才能评论

评论列表(0条)

保存