用模板实现替换预处理器 #ifdef 块
Replace preprocessor #ifdef blocks by template implementation
我有一堆代码,其中混杂着预处理器块,例如 #ifdef FEATURE_A
、#ifdef _MSC_VER
等等。
我想重构一些代码,以便用模板实现替换一些预处理器块。
编辑:
任务不是移除所有预处理器块,而是移除其中的一些,以摆脱混乱。
我不想用 foobar 的例子让你厌烦,所以这里有一个来自现实世界的例子(不是我的代码):
template <typename T>
std::string demangle()
{
#ifdef __GNUC__
size_t sz;
int status;
char* ptr = abi::__cxa_demangle(typeid(T).name(), 0, &sz, &status);
std::string name(ptr ? ptr : "", ptr ? strlen(ptr) : 0);
if(ptr){ free(ptr); }
std::string::size_type pos = name.rfind("::");
if(pos != std::string::npos)
{
name = name.substr(pos + 2);
}
#elif _MSC_VER
std::string name(typeid(T).name());
static const std::string struct_prefix("struct ");
static const std::string class_prefix("class ");
static const std::string ptr_postfix(" *");
std::string::size_type
at = name.find(struct_prefix);
if(at != std::string::npos) { name.erase(at, struct_prefix.size()); }
at = name.find(class_prefix);
if(at != std::string::npos) { name.erase(at, class_prefix.size()); }
at = name.find(ptr_postfix);
if(at != std::string::npos) { name.erase(at, ptr_postfix.size()); }
#else
std::string name(typeid(T).name());
#endif
return name;
}
问题 1:如何将其转换为等效的模板实现?
问题 2:为什么值得或不值得?
更新:
正如其他人所指出的,并且所提供的真实世界示例以优美的方式展示了它,在某些情况下无法摆脱预处理器。尤其是当 PP 涵盖像 abi::__cxa_demangle
这样的平台规范时。
但是,我是模板元编程的新手,所以有兴趣了解这种方法的优点和缺点。
这是我自己的解决方案,不幸的是,它的长度几乎是原始代码的 3 倍。这主要是因为解决方案需要一些辅助工具。
对于问题 1:
我使用枚举和 Int2Type
映射器将预处理器值转换为用户定义的类型。在第二步中,不同的部分被提取到部分专用的模板中。
这是重构后的代码示例(版本 2):
#include <iostream>
#include <cstring>
#include <stdlib.h>
#include <typeinfo>
enum CompilerIds
{
ANY = 0,
MSC = 1,
GNUC = 2
};
template <int v>
struct Int2Type
{
const static int value= v;
};
#ifdef __GNUC__
#include <cxxabi.h>
typedef Int2Type<GNUC> CompilerType;
#elif _MSC_VER
namespace abi
{
char* __cxa_demangle(const char* name, int n, size_t* sz, int* status);
}
typedef Int2Type<MSC> CompilerType;
#else
typedef Int2Type<ANY> CompilerType;
#endif
template <int N>
struct compiler_traits
{
static std::string demangle(std::string name)
{
return name;
}
};
template <>
struct compiler_traits<GNUC>
{
static std::string demangle(std::string name)
{
size_t sz;
int status;
char* ptr = abi::__cxa_demangle(name.c_str(), 0, &sz, &status);
std::string retName(ptr ? ptr : "", ptr ? strlen(ptr) : 0);
if(ptr){ free(ptr); }
std::string::size_type pos = retName.rfind("::");
if(pos != std::string::npos)
{
retName = retName.substr(pos + 2);
}
return retName;
}
};
template <>
struct compiler_traits<MSC >
{
static std::string demangle(std::string name)
{
static const std::string struct_prefix("struct ");
static const std::string class_prefix("class ");
static const std::string ptr_postfix(" *");
std::string::size_type
at = name.find(struct_prefix);
if(at != std::string::npos) { name.erase(at, struct_prefix.size()); }
at = name.find(class_prefix);
if(at != std::string::npos) { name.erase(at, class_prefix.size()); }
at = name.find(ptr_postfix);
if(at != std::string::npos) { name.erase(at, ptr_postfix.size()); }
return name;
}
};
template <typename T, typename traits = compiler_traits<CompilerType::value> >
struct demangle
{
static std::string Exec()
{
return traits::demangle(typeid(T).name());
}
};
int main()
{
std::cout << "\n mangled:" << typeid(Int2Type<GNUC>).name();
std::cout << "\n demangled:" << demangle<Int2Type<GNUC> >::Exec() <<"\n";
std::cout << "\n instatiate the msc version:" << compiler_traits<MSC>::demangle("struct Int2Type<2>") <<"\n";
return 0;
}
gcc 4.9.2 的输出是:
mangled:8Int2TypeILi2EE
demangled:Int2Type<2>
instatiate the msc version:Int2Type<2>
我不得不通过 MS 编译器的前向声明来破解 abi::__cxa_demangle
(在生产代码中永远不会这样做)。您会看到,compiler_traits<GNUC>
在 MS 系统上的实例化将失败。
第2题:
原则上可以用模板替换PP块,但也可能存在一些严重的缺点。
我仍然倾向于给它一个机会,它值得付出努力,尤其是当你没有 "platform stuff",但功能切换诸如 #ifdef FEATURE_A
之类的东西时。
使用模板:
- 扩展非常简单,不会破坏 OCP
- 它提高了可读性,特别是如果你有很多#ifdef
- 其他方式无法访问的代码变得可测试
这不可能。 C++ 模板使用两阶段查找 (Two phase name lookup for C++ templates - Why?),因此在函数模板中使用的任何不依赖于模板参数的名称必须在声明时可用。
您的 GCC 实现使用名称 abi::__cxa_demangle
,因此任何不提供该名称的实现都应该拒绝您的代码(有些可能不会,但这只是因为它们没有正确实现两阶段查找:What exactly is "broken" with Microsoft Visual C++'s two-phase template instantiation?).
解决此问题的唯一方法是在预处理器 #ifdef
块中包含对 abi::__cxa_demangle
的使用,这实际上意味着无论如何都要使用您的原始实现。
我有一堆代码,其中混杂着预处理器块,例如 #ifdef FEATURE_A
、#ifdef _MSC_VER
等等。
我想重构一些代码,以便用模板实现替换一些预处理器块。
编辑: 任务不是移除所有预处理器块,而是移除其中的一些,以摆脱混乱。
我不想用 foobar 的例子让你厌烦,所以这里有一个来自现实世界的例子(不是我的代码):
template <typename T>
std::string demangle()
{
#ifdef __GNUC__
size_t sz;
int status;
char* ptr = abi::__cxa_demangle(typeid(T).name(), 0, &sz, &status);
std::string name(ptr ? ptr : "", ptr ? strlen(ptr) : 0);
if(ptr){ free(ptr); }
std::string::size_type pos = name.rfind("::");
if(pos != std::string::npos)
{
name = name.substr(pos + 2);
}
#elif _MSC_VER
std::string name(typeid(T).name());
static const std::string struct_prefix("struct ");
static const std::string class_prefix("class ");
static const std::string ptr_postfix(" *");
std::string::size_type
at = name.find(struct_prefix);
if(at != std::string::npos) { name.erase(at, struct_prefix.size()); }
at = name.find(class_prefix);
if(at != std::string::npos) { name.erase(at, class_prefix.size()); }
at = name.find(ptr_postfix);
if(at != std::string::npos) { name.erase(at, ptr_postfix.size()); }
#else
std::string name(typeid(T).name());
#endif
return name;
}
问题 1:如何将其转换为等效的模板实现?
问题 2:为什么值得或不值得?
更新:
正如其他人所指出的,并且所提供的真实世界示例以优美的方式展示了它,在某些情况下无法摆脱预处理器。尤其是当 PP 涵盖像 abi::__cxa_demangle
这样的平台规范时。
但是,我是模板元编程的新手,所以有兴趣了解这种方法的优点和缺点。
这是我自己的解决方案,不幸的是,它的长度几乎是原始代码的 3 倍。这主要是因为解决方案需要一些辅助工具。
对于问题 1:
我使用枚举和 Int2Type
映射器将预处理器值转换为用户定义的类型。在第二步中,不同的部分被提取到部分专用的模板中。
这是重构后的代码示例(版本 2):
#include <iostream>
#include <cstring>
#include <stdlib.h>
#include <typeinfo>
enum CompilerIds
{
ANY = 0,
MSC = 1,
GNUC = 2
};
template <int v>
struct Int2Type
{
const static int value= v;
};
#ifdef __GNUC__
#include <cxxabi.h>
typedef Int2Type<GNUC> CompilerType;
#elif _MSC_VER
namespace abi
{
char* __cxa_demangle(const char* name, int n, size_t* sz, int* status);
}
typedef Int2Type<MSC> CompilerType;
#else
typedef Int2Type<ANY> CompilerType;
#endif
template <int N>
struct compiler_traits
{
static std::string demangle(std::string name)
{
return name;
}
};
template <>
struct compiler_traits<GNUC>
{
static std::string demangle(std::string name)
{
size_t sz;
int status;
char* ptr = abi::__cxa_demangle(name.c_str(), 0, &sz, &status);
std::string retName(ptr ? ptr : "", ptr ? strlen(ptr) : 0);
if(ptr){ free(ptr); }
std::string::size_type pos = retName.rfind("::");
if(pos != std::string::npos)
{
retName = retName.substr(pos + 2);
}
return retName;
}
};
template <>
struct compiler_traits<MSC >
{
static std::string demangle(std::string name)
{
static const std::string struct_prefix("struct ");
static const std::string class_prefix("class ");
static const std::string ptr_postfix(" *");
std::string::size_type
at = name.find(struct_prefix);
if(at != std::string::npos) { name.erase(at, struct_prefix.size()); }
at = name.find(class_prefix);
if(at != std::string::npos) { name.erase(at, class_prefix.size()); }
at = name.find(ptr_postfix);
if(at != std::string::npos) { name.erase(at, ptr_postfix.size()); }
return name;
}
};
template <typename T, typename traits = compiler_traits<CompilerType::value> >
struct demangle
{
static std::string Exec()
{
return traits::demangle(typeid(T).name());
}
};
int main()
{
std::cout << "\n mangled:" << typeid(Int2Type<GNUC>).name();
std::cout << "\n demangled:" << demangle<Int2Type<GNUC> >::Exec() <<"\n";
std::cout << "\n instatiate the msc version:" << compiler_traits<MSC>::demangle("struct Int2Type<2>") <<"\n";
return 0;
}
gcc 4.9.2 的输出是:
mangled:8Int2TypeILi2EE
demangled:Int2Type<2>
instatiate the msc version:Int2Type<2>
我不得不通过 MS 编译器的前向声明来破解 abi::__cxa_demangle
(在生产代码中永远不会这样做)。您会看到,compiler_traits<GNUC>
在 MS 系统上的实例化将失败。
第2题:
原则上可以用模板替换PP块,但也可能存在一些严重的缺点。
我仍然倾向于给它一个机会,它值得付出努力,尤其是当你没有 "platform stuff",但功能切换诸如 #ifdef FEATURE_A
之类的东西时。
使用模板:
- 扩展非常简单,不会破坏 OCP
- 它提高了可读性,特别是如果你有很多#ifdef
- 其他方式无法访问的代码变得可测试
这不可能。 C++ 模板使用两阶段查找 (Two phase name lookup for C++ templates - Why?),因此在函数模板中使用的任何不依赖于模板参数的名称必须在声明时可用。
您的 GCC 实现使用名称 abi::__cxa_demangle
,因此任何不提供该名称的实现都应该拒绝您的代码(有些可能不会,但这只是因为它们没有正确实现两阶段查找:What exactly is "broken" with Microsoft Visual C++'s two-phase template instantiation?).
解决此问题的唯一方法是在预处理器 #ifdef
块中包含对 abi::__cxa_demangle
的使用,这实际上意味着无论如何都要使用您的原始实现。