C++模板类继承

4

如何定义一个继承自模板类的模板类?

我想将std::queuestd::priority_queue包装到一个基类中,我的情况是LooperQueue。 我这样使用StdQueueauto queue = new StdQueue<LooperMessage *>()

我的类定义让编译器抱怨。

错误日志:

  In file included from /Users/rqg/ASProjects/PboTest/muses/src/main/cpp/Painter.cpp:10:
  /Users/rqg/ASProjects/PboTest/muses/src/main/cpp/util/StdQueue.h:14:5: error: unknown type name 'size_type'; did you mean 'size_t'?
      size_type size() override;
      ^~~~~~~~~
      size_t
  /Users/rqg/Library/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/5.0.300080/include/stddef.h:62:23: note: 'size_t' declared here
  typedef __SIZE_TYPE__ size_t;
                        ^
  In file included from /Users/rqg/ASProjects/PboTest/muses/src/main/cpp/Painter.cpp:10:
  /Users/rqg/ASProjects/PboTest/muses/src/main/cpp/util/StdQueue.h:16:5: error: unknown type name 'reference'
      reference front() override;
      ^
  /Users/rqg/ASProjects/PboTest/muses/src/main/cpp/util/StdQueue.h:20:21: error: unknown type name 'value_type'; did you mean 'ARect::value_type'?
      void push(const value_type &x) override;
                      ^~~~~~~~~~
                      ARect::value_type
  /Users/rqg/Library/Android/sdk/ndk-bundle/sysroot/usr/include/android/rect.h:44:21: note: 'ARect::value_type' declared here
      typedef int32_t value_type;

代码:

#ifndef PBOTEST_LOOPERQUEUE_H
#define PBOTEST_LOOPERQUEUE_H

#include <queue>
#include <cstdlib>

template<typename Tp, typename Sequence = std::deque<Tp> >
class LooperQueue {
  public:

    typedef typename Sequence::value_type                value_type;
    typedef typename Sequence::reference                 reference;
    typedef typename Sequence::const_reference           const_reference;
    typedef typename Sequence::size_type                 size_type;
    typedef          Sequence                            container_type;


    virtual size_type size()  = 0;

    virtual reference front() = 0;

    virtual void pop()= 0;

    virtual void push(const value_type &x) = 0;
};

#endif //PBOTEST_LOOPERQUEUE_H

#ifndef PBOTEST_STDQUEUE_H
#define PBOTEST_STDQUEUE_H

#include "LooperQueue.h"

template<typename Tp, typename Sequence = std::deque<Tp> >
class StdQueue : public LooperQueue<Tp, Sequence> {
  public:
    size_type size() override;

    reference front() override;

    void pop() override;

    void push(const value_type &x) override;
    
  private:
    std::queue<Tp, Sequence> mQueue;
};

#endif //PBOTEST_STDQUEUE_H

2
发布编译器错误时请附上文件名和行号,这样更好。另外,我建议使用“using”而不是“typedef”。语法更清晰,有时也可以提供更好的错误消息。 - xaxxon
1
此外,您不允许使用以下划线后跟大写字母或以两个下划线开头的名称。例如,_Sequence是不允许的。https://dev59.com/KHVC5IYBdhLWcg3woSxW#228797 - xaxxon
2
@xaxxon 任何标识符中间有两个连续的下划线是不允许的。 - Ryan Haining
@xaxxon 对于我的错误日志帖子感到抱歉。我已经删除了下划线,但错误仍然存在。我不理解“使用using而不是typedef”的含义。是否有更多细节可以提供? - Fantasy_RQG
3个回答

8

我将使用一个更简单的例子来说明,它会给你同样的错误。考虑一个只定义了一个别名的基类和一个试图使用它的子类:

template <typename T>
class Base {
 public:
  using value_type = T;
};

template <typename T>
class Derived : public Base<T> {
  value_type func();  // error
};

由于模板的复杂性,编译器此时无法知道value_type是什么。您需要通过限定它来告诉编译器它来自于Base类:
template <typename T>
class Derived : public Base<T> {
  typename Base<T>::value_type func();
};

或者通过使用using声明来告诉编译器,你打算使用一个基类类型别名。
template <typename T>
class Derived : public Base<T> {
  using typename Base<T>::value_type;
  value_type func();
};

编译器在知道 T 是什么并实例化模板之前,实际上无法知道 Base<T> 包含一个 value_type。为什么不能只查看Base模板呢?这样的事情在理论上是可能的,但它不知道哪些特化将可用。如果你在其他地方有以下代码:
template<>
class Base<int> {};

那么Derived<int>将不得不在其作用域中寻找value_type,这正是您原始代码中所做的。它尝试查找value_type但却失败了。这种行为可能导致一些令人惊讶的结果:

using value_type = char;
template <typename T>
class Derived : public Base<T> {
  value_type func(); // this is the global value_type = char, always
};

如果您需要更加深入的了解相关主题,可以阅读我的Medium文章


3
问题在于基类LooperQueue是依赖于模板参数TpSequence的一个依赖基类,因此它的完整类型无法在不知道模板参数的情况下确定。标准C++规定,非依赖名称(如size_typereferencevalue_type)不会在依赖于基类中查找。
要修正代码,只需通过基类名称限定名称;然后这些名称只能在实例化时查找,并且在那时将知道必须探索的确切基础专业化。例如:
template<typename _Tp, typename _Sequence = std::deque<_Tp> >
class StdQueue : public LooperQueue<_Tp, _Sequence> {
public:
    typename LooperQueue<_Tp, _Sequence>::::size_type size() override;
    typename LooperQueue<_Tp, _Sequence>::reference front() override;
    void pop() override;
    void push(const typename LooperQueue<_Tp, _Sequence>::value_type &__x) override;
private:
    std::queue<_Tp, _Sequence> mQueue;
};

我可以在 *.h 文件中定义 StdQueue,并在 *.cpp 文件中实现它吗? - Fantasy_RQG
1
@Fantasy_RQG 请查看为什么模板只能在头文件中实现? - songyuanyao

1

如果不是所有,那么大多数编译错误都是由于在派生类的名称查找期间未检查基类中的类型所导致的。C++标准规定应该完全限定类型名称(请参见this question)。换句话说,在没有完全限定的情况下,模板基类中的类型在派生类中是不可见的。下面是一个简单的示例,可以使用g++-6.3.0 -std=c++14进行编译和运行:

#include <iostream>
#include <deque>

using namespace std;

template <typename T, typename S = deque<T> >
class Base
{
public:
    typedef typename S::size_type size_type;

    virtual size_type size() = 0;
};


template <typename T, typename S = deque<T> >
class MyClass : public Base<T, S>
{
public:
    // type name has to be fully qualified
    typedef typename Base<T,S>::size_type size_type;

    // you could use "typename Base<T,S>::size_type" here instead
    size_type size() override { return 0; }
};


int main()
{
    MyClass<int> c;
    cout << c.size() << endl;
}

typedef typename Base<T,S>::size_type size_type; can be accomplished with using typename Base<T,S>::size_type; - Ryan Haining
是的,所有的 typedef 都可以用 using(别名声明)来实现,而我更喜欢使用别名声明而不是 typedef。然而,为了让示例更接近 Fantasy_RQG 的原始代码,我决定保留 typedef - Luis Guzman
你的代码很棒,但我无法将函数定义拆分为声明和定义。有没有什么办法可以做到这一点? - Fantasy_RQG
1
为了将函数声明与定义分离,首先将定义留在类内部。它看起来像这样:size_type size() override; 然后,在类之后或.C文件中:template <typename T, typename S> typename MyClass<T,S>::size_type MyClass<T,S>::size() { return 0; } 当然,您可以在不同的行上编写定义。如果您希望我编辑答案中的代码以反映此内容,请告诉我。 - Luis Guzman
1
顺便提一句,如果您将模板定义放在“.C”或“.cpp”文件中,您需要为特定类型显式实例化模板类,这是有限制的(也是一个不同的主题)。我通常只将定义放在“.h”文件中。 - Luis Guzman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接