1

假设我正在编写一个库,并且在某些时候,有一些代码想要对用户提供的类型的对象做一些特定的事情。举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?(函数,主要类模板)调用哪个的顺序有什么不同吗?这两种实现中的每一种都有哪些优点/缺点?

4

0 回答 0