GCC 与 MSVC 中的成员函数模板特化

tech

This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.

项目中的一部分实现要求高性能并且稳定,于是准备使用代码分离,高性能和可用性的部分用 C++ 完成,然后编译成 Python 模块,与业务层实现对接。

我在这里使用了 Boost::Python 来进行 C++ 代码的导出。

由于自己在 C++ 方面算是个新手,尤其是在对编译器的工作方面理解的不够深刻,于是在 Linux 中决定自己手动进行编译、链接的工作。果然,第一次编译就喜提了一大堆的 error 与 warning… 我在 MSVC 中赶紧把编译器警告级别提高到了 W4,根据相关的提示信息进行了修改,现在还剩下一个让我十分不解的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class Iterator {
private:
byte* current;

public:
// 按字节反转一段内存
static void _reverse(void* ptr, const size_t size) {
if (ptr == nullptr) return;
if (size < 2) return;
char* begin = static_cast<char*>(ptr);
char* end = begin + size - 1;
for (; begin < end; ++begin, --end) {
char memory = *begin;
*begin = *end; *end = memory;
}
}

public:
Iterator(const byte* ptr) {
this->current = const_cast<byte*>(ptr);
};

public:
template <typename type>
type get(bool endian = false, size_t size = -1) {
// size 表示手动指定迭代数据的长度, 当为-1时表示自动获取
// indian 表示是否进行大小端转换, 当为 true 时进行

if (size == -1) size = sizeof(type);
byte* buffer = new byte[size];
std::memmove(buffer, this->current, size);

// 进行大小端转换
if (endian == true) this->_reverse(buffer, size);
type variable(*reinterpret_cast<type*>(buffer));

// 将内部指针移动
this->current += size;
delete[] buffer;
return variable;
}

template<> inline std::string get<std::string>(bool endian, size_t size) {
// size 表示手动指定迭代数据的长度, 当为-1时表示自动获取
// indian 表示是否进行大小端转换, 当为 true 时进行
// 特化的get函数 - 针对std::string
char* buffer = new char[size];
std::memmove(buffer, this->current, size);

// string 不需要进行大小端转换
std::string variable(buffer);

// 移动内部指针
this->current += size;
delete[] buffer;
return variable;
}
};

上面的这一段代码是用来在字节流中格式化出指定的变量,特化的模板函数用于针对 std::string 类型给出结果,在 Windows 平台它工作的很好,编译器没有给出 warning,但当我试图在 gcc 中编译时,却给出了这样的错误:

GCC error: explicit specialization in non-namespace scope

在阅读了上述的解答之后,我知道了,特化的模板函数在 gcc 中是不能直接在类的声明中给出的,需要在类外实现。

1
2
3
template<> inline std::string Iterator::get<std::string>(bool endian, size_t size) {
... // 这里省略
}

经过一番 Debug 之后成功的在 GCC 中编译、链接通过了。

可是在使用 Python 调用时,却出现了 Memory Error

因为工程不算小,我经过了蛮久才定位出了出错的位置:所有调用了这个特化的模板函数的操作都会引起 Memory Error ,而这段代码应该是没什么问题的,因为在 Windows 平台的工作一切正常。

可这又是为什么呢?

模板函数在编译之后和普通函数一样,存在于整个程序的代码区,这部分也是有地址的,代码在调用时其实是调用了函数的地址(指针),将参数入栈进行函数的调用,这部分如果有问题是在编译期间就能发现的;而 Python 这里的 Memory Error 除了内存的 malloc 错误应该就是只对应的函数地址不存在相应的代码。

针对这一点,我便有了一个想法:如果我将函数声明为 inline,在对应的调用地方就会将这段代码进行展开,于是便可能消除这个错误。

我是幸运的,当我将这个成员模板函数声明为 inline 之后,Python 中的调用便成功了。

所以建议大家,如果在 GCC 编译之后,成员模板函数的特化出现了各种奇怪的问题,不妨尝试将函数声明为 inline 来进行 debug;如果函数体较大,可以在 debug 之后删除 inline 标志。

Author: 桂小方

Permalink: https://init.blog/1666/

文章许可协议:

如果你觉得文章对你有帮助,可以 支持我

Comments