假设我正在编写一个库,并且在某些时候,有一些代码想要对用户提供的类型的对象做一些特定的事情。举swap
个例子,虽然函数可以是任意的。重要的是:
- 这应该是静态多态性(编译时决定)
- 该库可以提供一个默认实现,即:
- 适用于许多情况(适用于大多数类型)
- OR 适用于所有类型,但可能对某些类型有更有效的实现
- 库的用户应该能够替换这个默认实现
在编译时在 C++ 中有两种主要的方法来做这样的事情:
- 函数重载
- 类模板特化
这些都不是理想的:
- 如果用户定义的类型是类模板,则函数重载是不合适的。功能模板不能部分特化,拥有多个功能模板需要大量的维护工作。
- 类模板不能很好地处理重载完全没问题的琐碎情况。
因此,我的想法是——为什么不两者兼而有之?事实上,一些 STL 功能提供了 2 种甚至更多的自定义行为方式。典型的 STL 算法提供 3 个:
- 提供将用作仿函数实现的附加函数参数(通常是 lambda)
- 为特定类型重载特定运算符
- 专门属于标准库的特定类模板
在我的情况下,第一个选项(额外参数)是不可能的,因为假定的库不会立即使用该函子。
所以我剩下两种可能的实现重载+专业化的方法:
- A)库调用函数,默认重载调用主类模板(首先解决重载,然后是类模板)
- B) 库调用初级类模板(首先解决类模板特化)和初级类模板实现调用(可能重载)函数
// A
namespace lib {
template <typename T>
struct swapper // can be specialized
{
void operator()(T& lhs, T& rhs) const noexcept
{
// default impl
}
};
template <typename T>
void swap(T& lhs, T& rhs) noexcept // can be overloaded
{
swapper<T>()(lhs, rhs);
}
template <typename T>
void core_function(/* ... */)
{
T t1 = /* ... */;
T t2 = /* ... */;
/* ... */
swap(t1, t2);
/* ... */
}
}
// B
namespace lib {
template <typename T>
void swap(T& lhs, T& rhs) noexcept // can be overloaded
{
// default impl
}
template <typename T>
struct swapper // can be specialized
{
void operator()(T& lhs, T& rhs) const noexcept
{
swap(lhs, rhs);
}
};
template <typename T>
void core_function(/* ... */)
{
T t1 = /* ... */;
T t2 = /* ... */;
/* ... */
swapper<T>()(t1, t2);
/* ... */
}
}
// user code (the same for both)
namespace user {
struct user_type { /* ... */ };
// customization through overloading
void swap(user_type& lhs, user_type& rhs) noexcept
{
// ...
}
// customization through class template specialization
template <>
struct swapper<user_type>
{
void swap(user_type& lhs, user_type& rhs) const noexcept
{
// ...
}
};
}
问题是 - 哪个实现更好 - A 还是 B?(函数,主要类模板)调用哪个的顺序有什么不同吗?这两种实现中的每一种都有哪些优点/缺点?