简体   繁体   English

关于模板专业化和继承的良好实践

[英]Good practices regarding template specialization and inheritance

Template specialization does not take into account inheritance hierarchy. 模板特化不考虑继承层次结构。 For example, if I specialize a template for Base and instantiate it with Derived , the specialization will not be chosen (see code (1) below). 例如,如果我专门为Base创建模板并使用Derived实例化它,则不会选择特化(参见下面的代码(1))。

This can be a major hindrance, because it sometimes lead to violation of the Liskov substitution principle. 这可能是一个主要障碍,因为它有时会导致违反Liskov替代原则。 For instance, while working on this question , I noticed that I could not use Boost.Range algorithms with std::sub_match while I could with std::pair . 例如,在处理这个问题时 ,我注意到我无法使用std::sub_match Boost.Range算法,而我可以使用std::pair Since sub_match inherits publicly from pair , common sense would dictate that I could substitute a sub_match everywhere a pair is used, but this fails due to trait classes using template specialization. 由于sub_matchpair公开继承,常识将指示我可以在使用pair任何地方替换sub_match ,但是由于使用模板特化的trait类而失败。

We can overcome this issue by using partial template specialization along with enable_if and is_base_of (see code (2)). 我们可以通过使用部分模板特化以及enable_ifis_base_of来解决此问题(请参阅代码(2))。 Should I always favor this solution over full specialization, especially when writing library code? 我是否总是喜欢这种解决方案而不是完全专业化,特别是在编写库代码时? Are there any drawbacks to this approach that I have overseen? 我监督这种方法有什么缺点吗? Is it a practice that you use or have seen used often? 这是您经常使用或经常使用的做法吗?


Sample codes 示例代码

(1)
#include <iostream>

struct Base {};
struct Derived : public Base {};

template < typename T >
struct Foo
{
    static void f() { std::cout << "Default" << std::endl; }
};

template <>
struct Foo< Base >
{
    static void f() { std::cout << "Base" << std::endl; }
};

int main()
{
    Foo<Derived>::f(); // prints "Default"
}

(2)
#include <type_traits>
#include <iostream>

struct Base {};
struct Derived : public Base {};

template <typename T, typename Enable = void>
struct Foo
{
    static void f() { std::cout << "Default" << std::endl; }
};

template <typename T>
struct Foo<
    T, typename 
    std::enable_if< std::is_base_of< Base, T >::value >::type
>
{
    static void f() { std::cout << "Base" << std::endl; }
};

int main()
{
    Foo<Derived>::f(); // prints "Base"
}

enable_if is more flexible enable_if更灵活

I think you should really prefer the enable_if approach: it enables everything that you could require and more. 我认为您应该更喜欢enable_if方法:它可以支持您需要的所有内容以及更多功能。

Eg there might be cases where a Derived class is Liskov-Subsitutable for a Base, but you [cannot assume/donot want to apply] the same traits/specializations to be valid (eg because the Base is POD class, whereas Derived ands non-POD behaviour or somehting like that that is completely orthogonal to class composition). 例如,可能存在Derived类是Liskov-Subsitutable for Base的情况, 您[不能假设/不想要应用]相同的特征/特化是有效的(例如因为Base是POD类,而Derived和非非) POD行为或类似于与类成分完全正交的行为。

enable_if gives you the power to define exactly the conditions. enable_if使您能够准确定义条件。

Hybrid approach 混合方法

You could also achieve some middleground by implementing a traits class that derives some application-specific traits from general-purpose traits. 您还可以通过实现一个traits类来实现一些中间件,该类从通用特征派生一些特定于应用程序的特征。 The 'custom' traits could use the enable_if and meta-programming techniques to apply traits as polymorphically as you desire. “自定义”特征可以使用enable_if和元编程技术,根据需要以多态方式应用特征。 That way, your actual implementations do not have to repeat some complicated enable_if/dispatch dance but instead can simply consume the custom-traits class (that hides the complexity). 这样,您的实际实现不必重复一些复杂的enable_if / dispatch舞蹈,而是可以简单地使用custom-traits类(隐藏复杂性)。

I think some (many?) Boost libraries use the hybrid approach (I've seen it in some capacity where it bridges eg fusion/mpl, I think also various iterator traits in Spirit). 我认为一些(很多?)Boost库使用混合方法(我已经看到它以某种形式连接它,例如fusion / mpl,我认为Spirit中的各种迭代器特性)。

I personally like this approach because it can effectively isolate the 'plumbing' from the core-business of a library, making maintenance and documentation (!) a lot easier. 我个人喜欢这种方法,因为它可以有效地将“管道”与库的核心业务隔离开来,使维护和文档 (!)变得更加容易。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM