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

/ 0评 / 1

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

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

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

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 中是不能直接在类的声明中给出的,需要在类外实现。



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 标志。

知识共享许可协议
本作品采用知识共享署名 4.0 国际许可协议进行许可。

发表评论

电子邮件地址不会被公开。 必填项已用*标注