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)),而不必使用上述描述的语法。