• Serialization Implementation Notes(1)


    1.Partial Function Template Ordering

    PFTO问题:一些C++编译器可能不正确地支持部分函数模板排序(PFTO)。PFTO是C++中的一个功能,用于解决在多个模板可以匹配一个特定函数调用时,如何确定使用哪个函数模板的问题。这个特性有时会在某些编译器中引发问题

    假设你有一个自定义的C++模板类my_template,它如下定义:

    template <typename T>
    class my_template {
    public:
        T data;
        my_template(const T& value) : data(value) {}
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后,你有一个序列化库,它试图通过模板函数serialize来序列化不同类型的数据。但是,你遇到了一个问题:一些编译器对函数模板的选择机制(PFTO)有问题,导致编译错误。

    这是你原始的模板函数:

    template<class Archive, class T>
    void serialize(
        Archive & ar, 
        T & t, 
        const unsigned int file_version
    ){
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    而这是你尝试的修改,用于解决编译问题:

    template<class Archive, class T>
    void serialize(
        Archive & ar, 
        my_template<T> & t, 
        const unsigned int file_version
    ){
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    问题在于,一些编译器可能无法正确地处理两个模板函数,因为它们在参数方面太相似。这时,你可以通过将第一个模板函数的第三个参数类型从unsigned int改为unsigned long int来解决问题:

    template<class Archive, class T>
    void serialize(
        Archive & ar, 
        T & t, 
        const unsigned long int file_version
    ){
        // ...
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    现在,这两个模板函数的第三个参数类型不同,编译器不再产生冲突。这种修改允许你继续使用函数模板来处理不同类型的序列化操作,而不受编译器的干扰。

    需要注意的是,这个修改是为了解决编译器问题,而不是为了改变程序的行为。在实际应用中,你可以继续使用serialize函数来序列化不同类型的数据,而不必担心编译器的特殊行为。

    函数重载:修改后的代码使用函数重载。当使用第三个参数为0调用serialize函数时,编译器首先查找匹配第三个参数为整数的模板。如果找到匹配项,它将实例化并调用匹配的模板。如果没有找到匹配项,它会尝试通过将参数转换为其他类型来匹配其他模板。在这种情况下,它可以将第三个参数转换为long以匹配第一个模板,这现在是默认模板。

    未定义行为:这种解决方法依赖于已知为非符合标准的编译器的未定义行为。换句话说,不能保证这种方法在所有编译器上都有效。文中提到,截止到写作时,还没有遇到不支持这种解决方法的编译器。

    潜在问题:文章指出,使用这种解决方法可能会在正确支持PFTO的编译器中引发问题。为了解决这个问题,定义了一个宏BOOST_PFTO。对于不符合标准的编译器,它被定义为long,而对于符合标准的编译器,它被定义为空。然后,在修改的serialize函数中使用这个宏,以确保根据编译器对PFTO的支持来确定正确的行为。

    例子

    #include 
    #include 
    
    template<class Archive, class T>
    void serialize(
        Archive & ar, 
        T & t, 
        const unsigned BOOST_PFTO int file_version
    ){
        // 序列化或反序列化 t 的代码
        std::cout << "Serializing or deserializing data..." <<file_version<< std::endl;
    }
    
    int main() {
        int data = 42;
        
        // 调用 serialize 函数
        serialize(std::cout, data, 1);
        serialize(std::cout, data, 2.8);
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    输出

    Serializing or deserializing data...1
    Serializing or deserializing data...2
    
    • 1
    • 2

    2.Character Encoding

    字符编码与宽字符的问题比看上去要复杂得多。当前的库以3种格式(文本、二进制和XML)、宽字符和窄字符的方式来定义,并尝试在不同编译器库之间实现可移植性。在对所有这些因素进行了相当长时间的考虑之后,制定了以下默认编码规则。

    • 所有文本存档(即text_?archive)将在当前流的区域设置(locale)下生成文本输出。通常情况下,这不会对字符串数据产生任何更改。

    • 要使用Microsoft编译器生成二进制输出,流必须以ios::binary模式打开。如果不这样做,将导致输入流中的0x0d字符(回车符)在后面跟着0x0a字符(换行符)时被移除。这可能会损坏输入并使文件无法读取。在UNIX系统上,ios::binary不是必需的,如果使用了它,将被忽略。

    • 字符XML存档(即xml_oarchive)将根据当前流的区域设置生成XML输出,并对字符进行编码。

    • 宽字符XML存档(即xml_woarchive)将生成以UTF-8编码的文件。

    这个字符编码是通过在创建存档时更改存档所使用的I/O流的区域设置来实现的,而在存档结束后,流的区域设置会被还原到其原始值。但是,可以通过在打开存档时指定 boost::archive::no_codecvt 标志来覆盖此操作。在这种情况下,序列化库不会更改流的区域设置。

    需要注意的是,用于宽字符文本和XML存档的代码转换可能会更改存档中存储的std::string数据。假设将一个普通(多字节)字符字符串写入宽字符流。在写入之前,系统会使用当前区域设置将其转换为宽字符字符串,然后再写入。在读取时,它会被还原回(多字节)字符串。如果读取存档的平台的区域设置与写入流的平台不同,序列化过程可能会更改实际的字符串数据。为了避免这种情况,要么避免使用区域设置依赖的多字节字符串,要么确保在读取存档之前正确设置区域设置。

    如果要生成宽字符文本输出(例如,在Win32系统上使用16位字符),请执行以下步骤:

    • 打开宽字符流。
    • 更改流的区域设置以使用 boost::archive::codecvt_null< OStream::char_type >。
    • 使用标志 no_codecvt 创建存档。

    当然,输入过程必须是对称的,以便正确还原数据。

    3.Partial Template Specialization

    如果编译器不支持部分模板特化,那么将无法成功编译提供的代码示例。为了在不支持部分模板特化的编译器上编译这段代码,需要将参数中的const关键字移除。

    void f(A const* a, text_oarchive& oa)
    {
      oa << a;
    }
    
    • 1
    • 2
    • 3
    • 4

    为了让不支持部分模板特化的编译器能够编译这段代码,可以将参数修改为如下形式:

    void f(A* a, text_oarchive& oa)
    {
      oa << a;
    }
    
    • 1
    • 2
    • 3
    • 4

    这个修改会移除参数中的const关键字,以适应不支持部分模板特化的编译器,但需要谨慎确保这个修改不会影响代码的其他行为和语义。

    4.Template Invocation Syntax

    模板调用语法(Template Invocation Syntax)的问题是,有些编译器可能无法识别以下语法:

    ar.template register_type<T>();
    
    • 1

    这是用于注册多态类的派生指针的语法。实际的函数原型是:

    template <typename T>
    void register_type(T* t = nullptr);
    
    • 1
    • 2

    这样,可以使用以下语法来进行注册:ar.register_type(static_cast(nullptr)),而不必使用上述描述的语法。

  • 相关阅读:
    shell之df和du命令介绍
    【CMake】使用ctest配置googletest
    前后端加密解密工具类(前端rsa加密,后端进行解密)
    Windows环境如何ssh远程连接本地局域网内的Archcraft系统
    spark学习笔记(十一)——sparkStreaming-概述/特点/构架/DStream入门程序wordcount
    Vue2+elementui项目导出el-table的数据为xlsx表格
    NC20242 [SCOI2005]最大子矩阵
    SSM+网上书店管理系统 毕业设计-附源码082255
    【计算机组成原理】总线(三)—— 总线操作和定时
    网站、小程序常见布局样式记录
  • 原文地址:https://blog.csdn.net/qq_40178082/article/details/133172317