5.1 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) {}
};

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

这是你原始的模板函数:

template<class Archive, class T>
void serialize(
    Archive & ar, 
    T & t, 
    const unsigned int file_version
){
    // ...
}

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

template<class Archive, class T>
void serialize(
    Archive & ar, 
    my_template<T> & t, 
    const unsigned int file_version
){
    // ...
}

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

template<class Archive, class T>
void serialize(
    Archive & ar, 
    T & t, 
    const unsigned long int file_version
){
    // ...
}

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

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

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

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

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

例子

#include <iostream>
#include <boost/serialization/serialization.hpp>

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;
}

输出

Serializing or deserializing data...1
Serializing or deserializing data...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;
}

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

void f(A* a, text_oarchive& oa)
{
  oa << a;
}

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

4.Template Invocation Syntax

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

ar.template register_type<T>();

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值