【问题标题】:Static Polymorphism with CRTP: Using the Base Class to Call Derived MethodsCRTP 的静态多态性:使用基类调用派生方法
【发布时间】:2014-07-31 17:44:08
【问题描述】:

virtual 在 C++ 中的主要好处之一是能够使用基类(指针或引用)来调用派生方法。

我正在阅读using CRTP to implement static polymorphism,但我无法理解如何使用这种技术实现我上面提到的内容,因为当需要模板。

在我看来,文章中描述的内容可以通过简单地使用函数重载来实现,所以我相信这种技术肯定还有更多。

(PS:在对this question 的回答的评论中提到了这个确切的问题,但不幸的是没有人回复它:“vtables 真正提供的是使用基类(指针或引用)来调用派生方法。你应该在这里展示它是如何使用 CRTP 完成的。")

这是我的最小代码,它给出了错误“在‘&’标记之前缺少模板参数 无效打印(基础和对象)”。

#include <cstring>
#include <iostream>

template <typename Derived>
struct Base
{
    std::string ToStringInterface() { return static_cast<Derived*>(this)->ToString(); }

    std::string ToString()  {   return "This is Base.";     }
};

struct Derived : Base<Derived>
{
    std::string ToString()  {   return "This is Derived.";  }
};

void Print(Base& Object)
{
    std::cout << Object->ToStringInterface() << std::endl;
}

int main()
{
    Derived MyDerived;

    // This works, but could have been achieved with a function overload.
    std::cout << MyDerived.ToStringInterface() << std::endl;

    // This does not work.
    Print(MyDerived);
}

【问题讨论】:

  • “因为当需要模板时,我不能将函数声明为采用 Base 类型。” 函数Print 必须是静态多态的函数模板,是的-- 类似template&lt;class Derived&gt; void Print(Base&lt;Derived&gt;&amp; Object);。静态多态性在编译时解决,因此Print 必须确切地知道在编译时调用哪个函数。

标签: c++ templates polymorphism crtp


【解决方案1】:

感谢收到的 cmets 和答案,我正在发布我的实现,以防它对其他人有用。

#include <cstring>
#include <iostream>

template <typename Derived>
class Base
{
public:
    std::string ToStringInterface()
    {
        return static_cast<Derived*>(this)->ToString();
    }
};

template<>
class Base<void> : public Base<Base<void> >
{
public:
    std::string ToString()
    {
        return "This is Base (default implementation).";
    }
};

class Derived : public Base<Derived>
{
public:
    std::string ToString()
    { 
        return "This is Derived.";
    }
};

template <typename T>
void Print(Base<T>& Object)
{
    std::cout << Object.ToStringInterface() << std::endl;
}

int main()
{   
    int Decision;
    std::cout << "Do you want to create an object of type Base (input 0) or Derived (input 1)? ";
    std::cin >> Decision;
    if (Decision == 0)
    {
        Base<void> MyBase;
        Print(MyBase);
    }
    else
    {
        Derived MyDerived;
        Print(MyDerived);
    }
}

【讨论】:

    【解决方案2】:

    好吧,你需要声明一个 print 模板函数:

    template<class T>
    void Print(Base<T>& Object)
    {
        std::cout << Object.ToStringInterface() << std::endl;
    }
    

    【讨论】:

    • 正是我需要的。一个小的后续问题:在这种情况下我不能有 Base 的默认方法,并且不能实例化 Base 类型的对象,我是对的吗? (可以通过动态多态来完成)。
    • 您可以创建一个Base&lt;Whatever&gt; 类型的实例,它实际上不是Whatever,但调用ToStringInterface() 会调用未定义的行为。使ctor受保护和/或检查ToStringInterface()中的正确类型。请注意,并非总是可以使用dynamic_cast 进行检查,因为 CRTP 不一定涉及任何虚函数,但此类转换是必需的。
    【解决方案3】:

    抱歉,CRTP 确实不能那样工作。这个想法通常是以非常特定于 C++ 的方式将一些代码注入到依赖层次结构中。在您的示例中,您可以拥有例如需要ToStringInterface() 函数并使用CRTP 将其绑定到现有类层次结构的ToString() 的接口:

    class IStringable
    {
        virtual string ToStringInterface() = 0;
    };
    class Unchangeable
    {
        virtual string ToString();
    };
    template<class Derived>
    class UnchangeableToIStringableMixin
    {
        virtual string ToStringInterface()
        {
            return static_cast<Derived*>(this)->ToString();
        }
    };
    class StringableUnchangeable:
        public Unchangeable, UnchangeableToIStringableMixin<StringableUnchangeable>
    {
    };
    

    但是,如果 Unchangeable 真的可以更改,您就不会这样做。不要忘记考虑 CRTP 可能不是您正在做的事情的正确工具。

    【讨论】:

    • 谢谢。我这样做更多的是作为模板练习并满足我的好奇心,而不是其他任何事情。在实践中,我不明白为什么我会处理所有这些复杂性,而不仅仅是使用virtual。最终,我找到了一个我喜欢的解决方案,通过使用 void 专门化 Base 类(见下文)。
    • @Ulrich 恕我直言,您不需要在成员函数上使用 virtual,即使这是 CRTP 的主要优势,在编译时进行虚拟操作,您将拥有更快的 exec,因为它不需要在运行时浏览虚拟表。
    猜你喜欢
    • 2011-08-25
    • 2012-05-24
    • 2015-04-08
    • 2010-10-10
    • 2012-06-25
    • 2013-04-24
    相关资源
    最近更新 更多