使用模板摆脱动态链接样板文件:extern "C" 和模板不要混用;
Using templates to get rid of dynamic linking boilerplate: extern "C" and templates don't mix;
我想到了一些 C++ 代码,起初我并没有考虑太多,直到我注意到 clang 接受它但 gcc 不接受。我越想越觉得 gcc 可能是对的。问题是,如果没有大量样板文件,我真的想不出一个好的、合法的方法来做我想做的事。
首先,这是我希望用 g++ 编译的代码:
extern "C" template <typename T>
using factory_pointer_t = T* (*)();
因为我认为这可能是一个 XY 问题,所以有点背景知识。我正在开发一个界面相当简单的小插件系统。基本上它只包含一个工厂函数,return 是一个指向抽象基础 class 的指针。抽象基 class 要求处理内存管理的方法,类似于 COM。像这样:
class ITest {
public:
virtual void release() = 0;
virtual void test_method() = 0;
protected:
virtual ~ITest(){}
};
这将在动态库中实现,如下所示:
class Test : public ITest {
public:
virtual void release() override {
delete this;
};
virtual void test_method() override {
std::cout << "Hello, shared World!\n";
}
};
extern "C" ITest * testFactory() {
return new Test{};
}
我对此所做的研究表明,这应该至少在 windows 和 linux 上跨工具链可靠地工作。即使事实并非如此,该系统旨在在一个工具链上进行快速原型设计,并有机会在某个地方静态地放弃插件和 link 一切。到目前为止没有问题。
我的其他研究表明,extern "C"
linkage 说明符对于动态 linkage 在支持相同 C ABI 的所有工具中正常工作非常重要。现在的问题是:
我想编写一些模板代码来帮助简化其中的一些操作。
但是 extern "C"
与模板语法的组合似乎不起作用。我为 Google 提供帮助的努力因有关已弃用 extern template
的文章而受阻。我想给它一个抽象接口的名称,让它加载 dll 并解析工厂函数的符号。但正如您所见,回调的类型 (factory_pointer_t<T>
) 由它们 returning 的抽象接口指针类型参数化。所以我的问题:
这个模板 using 指令真的可以编译吗? (它适用于 clang 但不是 gcc)
如果我将我的函数指针类型声明为:extern "C" typedef void* (*factory_pointer)();
并使用 reinterpret_cast<>
显式转换 return 值,我会调用未定义的行为吗?鉴于在 DLL 中,我想保持导出的函数签名不变。
我是否有任何其他我可能没有考虑过的不涉及 void*
的既定目标的选择?
函数指针的 typedef 和 extern "C"
是否绝对必要?标准似乎拿不定主意。例如:在 2014 年标准草案的 7.5 中:“两种具有不同语言的函数类型
linkages 是不同的类型,即使它们在其他方面相同。”但在 8.3.5 第 8 段中:“return 类型、参数类型列表、ref 限定符和 cv- qualifier-seq,但不是默认的
参数 (8.3.6) 或异常规范 (15.4) 是函数类型的一部分。"
我真的很想让它在其他编译器中自动工作(即我给它一个类型并告诉它加载什么库然后我就完成了),但我想我没有看到我需要做什么.每个 class 涉及超过一行样板的任何内容,我都会认为是不好的。
因为这是我第一次尝试动态 linking(不仅仅是让构建系统为我做),我很想得到一些好的建议。
总结: 我有一个基于第一行代码的工作解决方案,它只能在一个编译器中工作。我知道这可能是错误的,或者至少不是普遍支持的语法。我该如何正确操作?
- Should this template using directive actually compile? (It does on clang but not gcc)
我不确定。该标准说函数类型、函数名称和变量名称具有语言链接,但没有说明是否可以使用别名模板来生成此类函数类型。
EDG 也拒绝它,但 Clang 允许它可能是正确的。
编辑:我找不到 http://open-std.org/JTC1/SC22/WG21/docs/cwg_closed.html#1463 指出 [temp]/4 说
A template, a template explicit specialization (14.7.3), and a class template partial specialization shall not have C linkage.
所以从技术上讲,Clang 接受它是错误的,但是 Evolution Working Group 将考虑是否应该允许它。
- If I declare my function pointer type as:
extern "C" typedef void* (*factory_pointer)();
and cast the return value explicitly with a reinterpret_cast<>
, will I be invoking undefined behavior?
(编辑:我最初在这里说是,因为我看错了问题)
不,但是 static_cast
会比 reinterpret_cast
更好。
- Do I have any other options I might not have considered for accomplishing my stated goal that don't involve void*?
去掉所有的C语言链接。我认为这对您的情况没有必要或有用。
- Is a typedef with extern "C" for the function pointer strictly necessary? The standard can't seem to make up its mind. It says for example: in 7.5 of the 2014 draft standard: "Two function types with different language linkages are distinct types even if they are otherwise identical." but in 8.3.5 paragraph 8: "The return type, the parameter-type-list, the ref-qualifier, and the cv-qualifier-seq, but not the default arguments (8.3.6) or the exception specification (15.4), are part of the function type."
这并没有说明任何关于语言链接的内容,因此与 7.5 中非常明确的声明并不矛盾,该声明是类型的一部分。
另请注意:
[expr.call]/1: Calling a function through an expression whose function type has a language linkage that is different from the language linkage of the function type of the called function’s definition is undefined (7.5).
这是未定义的,因为如果例如 C 语言链接暗示与 C++ 语言链接不同的调用约定,它将不起作用。
但是大多数编译器都会做正确的事情,因为它们实际上并没有对 C 和 C++ 函数使用不同的调用约定,而且大多数甚至没有执行 C 语言链接的函数类型不同的规则。参见例如https://gcc.gnu.org/bugzilla/show_bug.cgi?id=2316
你绝对可以做到这一点(归功于 Potatoswatter for spotting this ability in his answer here):
extern "C" {
template <typename T>
class factory_pointer_def {
typedef T* (*type)();
};
}
可以结合
template <typename T>
using factory_pointer_t = typename factory_pointer_def<T>::type;
也可以这样写
extern "C" {
template <typename T>
using factory_pointer_t = T* (*)();
}
并获得预期的效果,尽管标准没有给出示例。然而,标准实际上说的是
A C language linkage is ignored in determining the language linkage of the names of class members and the function type of class member functions.
和alias-template两者都不是,所以C语言链接不会被忽略。
所以你需要改变的就是使用extern "C"
的块形式。
正如 Jonathan 指出的那样,提供模板 C 语言链接是非法的。但是
In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names with external linkage, and variable names with external linkage declared within the linkage-specification.
语言链接不适用于模板 class 或模板别名。仅针对函数指针声明中的函数类型。
因此,上述任何在 extern "C"
块中使用模板的代码都是完全合法的。
我想到了一些 C++ 代码,起初我并没有考虑太多,直到我注意到 clang 接受它但 gcc 不接受。我越想越觉得 gcc 可能是对的。问题是,如果没有大量样板文件,我真的想不出一个好的、合法的方法来做我想做的事。
首先,这是我希望用 g++ 编译的代码:
extern "C" template <typename T>
using factory_pointer_t = T* (*)();
因为我认为这可能是一个 XY 问题,所以有点背景知识。我正在开发一个界面相当简单的小插件系统。基本上它只包含一个工厂函数,return 是一个指向抽象基础 class 的指针。抽象基 class 要求处理内存管理的方法,类似于 COM。像这样:
class ITest {
public:
virtual void release() = 0;
virtual void test_method() = 0;
protected:
virtual ~ITest(){}
};
这将在动态库中实现,如下所示:
class Test : public ITest {
public:
virtual void release() override {
delete this;
};
virtual void test_method() override {
std::cout << "Hello, shared World!\n";
}
};
extern "C" ITest * testFactory() {
return new Test{};
}
我对此所做的研究表明,这应该至少在 windows 和 linux 上跨工具链可靠地工作。即使事实并非如此,该系统旨在在一个工具链上进行快速原型设计,并有机会在某个地方静态地放弃插件和 link 一切。到目前为止没有问题。
我的其他研究表明,extern "C"
linkage 说明符对于动态 linkage 在支持相同 C ABI 的所有工具中正常工作非常重要。现在的问题是:
我想编写一些模板代码来帮助简化其中的一些操作。
但是 extern "C"
与模板语法的组合似乎不起作用。我为 Google 提供帮助的努力因有关已弃用 extern template
的文章而受阻。我想给它一个抽象接口的名称,让它加载 dll 并解析工厂函数的符号。但正如您所见,回调的类型 (factory_pointer_t<T>
) 由它们 returning 的抽象接口指针类型参数化。所以我的问题:
这个模板 using 指令真的可以编译吗? (它适用于 clang 但不是 gcc)
如果我将我的函数指针类型声明为:
extern "C" typedef void* (*factory_pointer)();
并使用reinterpret_cast<>
显式转换 return 值,我会调用未定义的行为吗?鉴于在 DLL 中,我想保持导出的函数签名不变。我是否有任何其他我可能没有考虑过的不涉及
void*
的既定目标的选择?函数指针的 typedef 和
extern "C"
是否绝对必要?标准似乎拿不定主意。例如:在 2014 年标准草案的 7.5 中:“两种具有不同语言的函数类型 linkages 是不同的类型,即使它们在其他方面相同。”但在 8.3.5 第 8 段中:“return 类型、参数类型列表、ref 限定符和 cv- qualifier-seq,但不是默认的 参数 (8.3.6) 或异常规范 (15.4) 是函数类型的一部分。"
我真的很想让它在其他编译器中自动工作(即我给它一个类型并告诉它加载什么库然后我就完成了),但我想我没有看到我需要做什么.每个 class 涉及超过一行样板的任何内容,我都会认为是不好的。
因为这是我第一次尝试动态 linking(不仅仅是让构建系统为我做),我很想得到一些好的建议。
总结: 我有一个基于第一行代码的工作解决方案,它只能在一个编译器中工作。我知道这可能是错误的,或者至少不是普遍支持的语法。我该如何正确操作?
- Should this template using directive actually compile? (It does on clang but not gcc)
我不确定。该标准说函数类型、函数名称和变量名称具有语言链接,但没有说明是否可以使用别名模板来生成此类函数类型。
EDG 也拒绝它,但 Clang 允许它可能是正确的。
编辑:我找不到 http://open-std.org/JTC1/SC22/WG21/docs/cwg_closed.html#1463 指出 [temp]/4 说
A template, a template explicit specialization (14.7.3), and a class template partial specialization shall not have C linkage.
所以从技术上讲,Clang 接受它是错误的,但是 Evolution Working Group 将考虑是否应该允许它。
- If I declare my function pointer type as:
extern "C" typedef void* (*factory_pointer)();
and cast the return value explicitly with areinterpret_cast<>
, will I be invoking undefined behavior?
(编辑:我最初在这里说是,因为我看错了问题)
不,但是 static_cast
会比 reinterpret_cast
更好。
- Do I have any other options I might not have considered for accomplishing my stated goal that don't involve void*?
去掉所有的C语言链接。我认为这对您的情况没有必要或有用。
- Is a typedef with extern "C" for the function pointer strictly necessary? The standard can't seem to make up its mind. It says for example: in 7.5 of the 2014 draft standard: "Two function types with different language linkages are distinct types even if they are otherwise identical." but in 8.3.5 paragraph 8: "The return type, the parameter-type-list, the ref-qualifier, and the cv-qualifier-seq, but not the default arguments (8.3.6) or the exception specification (15.4), are part of the function type."
这并没有说明任何关于语言链接的内容,因此与 7.5 中非常明确的声明并不矛盾,该声明是类型的一部分。
另请注意:
[expr.call]/1: Calling a function through an expression whose function type has a language linkage that is different from the language linkage of the function type of the called function’s definition is undefined (7.5).
这是未定义的,因为如果例如 C 语言链接暗示与 C++ 语言链接不同的调用约定,它将不起作用。
但是大多数编译器都会做正确的事情,因为它们实际上并没有对 C 和 C++ 函数使用不同的调用约定,而且大多数甚至没有执行 C 语言链接的函数类型不同的规则。参见例如https://gcc.gnu.org/bugzilla/show_bug.cgi?id=2316
你绝对可以做到这一点(归功于 Potatoswatter for spotting this ability in his answer here):
extern "C" {
template <typename T>
class factory_pointer_def {
typedef T* (*type)();
};
}
可以结合
template <typename T>
using factory_pointer_t = typename factory_pointer_def<T>::type;
也可以这样写
extern "C" {
template <typename T>
using factory_pointer_t = T* (*)();
}
并获得预期的效果,尽管标准没有给出示例。然而,标准实际上说的是
A C language linkage is ignored in determining the language linkage of the names of class members and the function type of class member functions.
和alias-template两者都不是,所以C语言链接不会被忽略。
所以你需要改变的就是使用extern "C"
的块形式。
正如 Jonathan 指出的那样,提供模板 C 语言链接是非法的。但是
In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names with external linkage, and variable names with external linkage declared within the linkage-specification.
语言链接不适用于模板 class 或模板别名。仅针对函数指针声明中的函数类型。
因此,上述任何在 extern "C"
块中使用模板的代码都是完全合法的。