C++ 混合C++;同一项目中的风味

C++ 混合C++;同一项目中的风味,c++,c++11,cuda,compilation,compatibility,C++,C++11,Cuda,Compilation,Compatibility,在同一个项目中混合使用C++98和C++11安全吗?我所说的“混合”不仅指链接对象文件,还指使用C++98和C++11编译的源代码中包含的公共头文件 这个问题的背景是希望将大型代码库的至少一部分转换为C++11。代码的一部分是C++ C++语言,编译为在GPU或CPU上执行,相应的编译器此时不支持C++ 11。然而,大部分代码只用于CPU,并且可以用C++的味道编译。一些头文件包含在CPU+GPU和仅CPU源文件中 如果我们现在用C++11编译器编译只使用CPU的源文件,我们能确信不受不良副作用

在同一个项目中混合使用C++98和C++11安全吗?我所说的“混合”不仅指链接对象文件,还指使用C++98和C++11编译的源代码中包含的公共头文件

这个问题的背景是希望将大型代码库的至少一部分转换为C++11。代码的一部分是C++ C++语言,编译为在GPU或CPU上执行,相应的编译器此时不支持C++ 11。然而,大部分代码只用于CPU,并且可以用C++的味道编译。一些头文件包含在CPU+GPU和仅CPU源文件中

如果我们现在用C++11编译器编译只使用CPU的源文件,我们能确信不受不良副作用的影响吗?

在实践中,也许是这样

C++11和C++03的标准库在
std
namespace对象的布局上存在分歧是比较常见的。例如,
sizeof(std::vector)
在MSVC领域的各种编译器版本中发生了显著的变化。(随着他们的优化,它变得更小)

其他示例可能是编译器围栏两侧的不同堆

因此,您必须小心地在两个源代码树之间设置“防火墙”

现在,一些编译器试图最小化这种二进制兼容性更改,即使是以违反标准为代价。我相信没有大小计数器的
std::list
就是一个例子(这违反了C++11,但我记得至少有一家供应商提供了不符合标准的
std::list
,以保持二进制兼容性——我不记得是哪家了)

对于这两个编译器(C++03和C++11中的一个编译器是不同的编译器),您将获得一些ABI保证。ABI可能会同意该语言的大部分内容,在这一点上,您是相对安全的

为了安全合理,您将希望将其他编译器版本文件视为第三方DLL(延迟加载库),它们不链接到同一C++标准库。这意味着从一个传递到另一个的任何资源都必须使用销毁代码打包(即,返回到要销毁的DLL)。您要么调查两个标准库的ABI,要么避免在公共头文件中使用它,这样您就可以在DLL之间传递智能指针之类的东西

一种更安全的方法是使用其他代码库将自己剥离到一个C风格的接口,并且只在两个代码库之间传递句柄(不透明类型)。为了使这个问题变得明智,只需在一些漂亮的C++代码中只使用MJO来包装C风格的界面,就不要在代码库之间传递这些C++对象。 所有这些都是一种痛苦

例如,假设您有一个
std::string get_some_string(HANDLE)
函数,并且您不信任ABI稳定性

所以你有3层

namespace internal {
  // NOT exported from DLL
  std::string get_some_string(HANDLE) { /* implementation in DLL */ }
}
namespace marshal {
  // exported from DLL
  // visible in external headers, not intended to be called directly
  void get_some_string(HANDLE h, void* pdata, void(*callback)( void*, char const* data, std::size_t length ) ) {
    // implementation in DLL
    auto r = ::internal::get_some_string(h);
    callback( pdata, r.data(), r.size() );
  }
}
namespace interface {
  // exists in only public header file, not within DLL
  inline std::string get_some_string(HANDLE h) {
    std::string r;
    ::marshal::get_some_string(h, &r,
      [](void* pr, const char* str, std::size_t length){
        std::string& r = *static_cast<std::string*>(pr);
        r.append( str, length );
      }
    );
    return r;
  }
}
名称空间内部{
//未从DLL导出
string获取一些字符串(句柄){/*在DLL中实现*/}
}
名称空间封送{
//从DLL导出
//在外部标头中可见,不打算直接调用
void获取一些字符串(句柄h,void*pdata,void(*回调)(void*,char const*data,std::size\t length)){
//DLL中的实现
自动r=::内部::获取一些字符串(h);
回调(pdata,r.data(),r.size());
}
}
名称空间接口{
//仅存在于公共头文件中,而不存在于DLL中
内联std::string获取一些字符串(句柄h){
std::字符串r;
::封送:获取一些字符串(h,&r,
[](void*pr,const char*str,std::size\u t length){
std::string&r=*静态(pr);
r、 追加(str,长度);
}
);
返回r;
}
}
因此,DLL之外的代码执行一个
auto s=::interface::get_some_string(handle),它看起来像一个C++接口。
DLL中的代码实现
std::string::internal::get_some_string(句柄)

marshal
get_some_string
在两者之间提供了一个C风格的接口,它提供了比依赖
std::string
的布局和实现更好的二进制兼容性,从而在DLL和使用DLL的代码之间保持稳定


接口
std::string
完全存在于非DLL代码中。
internal
std::string
完全存在于DLL代码中。封送处理代码将数据从一端移动到另一端。

我曾经问过一个问题。关于
std::list
:那将是libstdc++(随gcc一起提供)。这个答案需要一个ODR的示例,它隐藏在一个编译器后面,手里拿着一个标有“未定义行为”的bat,等待着跳出来打败程序员。