【问题标题】:Forward-declaring templates that need each other前向声明需要彼此的模板
【发布时间】:2018-03-12 19:13:03
【问题描述】:

大学的一位教授希望我们使用 std::vector 实现一个堆栈,并为它编写一个“unstacking”迭代器(即,一个迭代器在迭代时弹出堆栈顶部)。
一切都可以很好,直到他还决定他希望所有这些都是通用的,使用模板和所有。那是地狱开始的时候。

所以我做的第一件事就是写template<typename T> class VectorStack

//file VectorStack.hpp
template <typename T>
class VectorStack
{
    public:
        VectorStack();
        virtual size_t size();
        virtual bool empty();
        virtual void push(T obj);
        virtual T pop();

    private:
        vector<T> _data;
};  

实现可能与此处无关,因此我将跳过它。不要犹豫,问你是否需要它。 然后我不得不写template&lt;typename T&gt; class VectorStackIterator...

  • 为了让该迭代器解压VectorStack 的实例,它必须至少包含一个指向该实例的指针。所以VectorStackIterator 需要了解VectorStack,这导致我们进行第一次前向声明。
  • 而且,VectorStack 具有 begin()end() 方法,它们应该返回 VectorStackIterator。所以VectorStack 还需要了解VectorStackIterator,这导致我们进行第二次前向声明。

所以我把我的迭代器写在一个单独的文件中:

//file VectorStackIterator.hpp
template<typename T>
class VectorStack;          //Forward declaration of the VectorStack

template<typename T>
class VectorStackIterator : public iterator<random_access_iterator_tag, VectorStack<T>>
{
    public:
        VectorStackIterator(size_t n, VectorStack<T>* instance);
        T operator--();
        bool operator==(VectorStackIterator other);
        bool operator!=(VectorStackIterator other);

    private:
        VectorStackIterator();
        T& operator=() {};

        size_t _n;
        VectorStack<T>* _instance;
};

...并将我的VectorStack 更新为如下所示:

//file VectorStack.hpp
template<typename T>
class VectorStackIterator;  //Forward declaration of the iterator

template <typename T>
class VectorStack
{
    public:
        //...

        VectorStackIterator<T> top();
        VectorStackIterator<T> bottom();

    //...
};  

同样,迭代器的实现可能不相关。
此时,我已经让编译器尖叫了,因为我到处都在使用incomplete types。所以我尝试了其他方法:我将VectorStackVectorStackIterator 的声明放在同一个文件的开头,只有这样,我把所有方法的定义.这是它的样子:

//file VectorStack.hpp
#ifndef VECTOR_STACK_HPP
#define VECTOR_STACK_HPP

#include <vector>
using std::vector;

#include <iterator>
using std::iterator;

#include <exception>
using std::out_of_range;

template <typename T>
class VectorStack;   
//still had to forward-declare this because VectorStackIterator uses it in its own declaration.

//Class declaration (VectorStackIterator)
template<typename T>
class VectorStackIterator : public iterator<random_access_iterator_tag, VectorStack<T>>
{
    public:
        VectorStackIterator(size_t n, VectorStack<T>* instance);
        T operator--();
        bool operator==(VectorStackIterator other);
        bool operator!=(VectorStackIterator other);

    private:
        VectorStackIterator();
        T& operator=() {};

        size_t _n;
        VectorStack<T>* _instance;
};

//Class declaration (VectorStack)
template <typename T>
class VectorStack
{
    public:
        VectorStack();
        virtual size_t size();
        virtual bool empty();
        virtual void push(T obj);
        virtual T pop();

        VectorStackIterator<T> top();
        VectorStackIterator<T> bottom();

    private:
        vector<T> _data;
};

所有这些都跟在上面声明的每个方法的定义之后。我不认为这就是错误所在,但请询问您是否希望我提供它。

这是我提出的最接近解决方案的尝试,但是当我在 main 函数中声明 VectorStack&lt;int&gt; 对象时,编译器仍然抱怨 Incomplete types not allowed here

#include "VectorStack.hpp"
int main(int argc, char** argv)
{
    VectorStack<int> v;            //Incomplete types not allowed here
    v.push(0);                     //Incomplete types not allowed here
    v.push(1);                     //Incomplete types not allowed here
    v.push(2);                     //Incomplete types not allowed here
    for (auto it = v.top(); it != v.bottom();) //Incomplete types not allowed here (x2)
    {
        cout << it-- << endl;
    }

    return 0;
}

如果我尝试前向声明迭代器而不是向量堆栈,则向量堆栈不再不完整,但迭代器是不完整的,并且我在 for 循环的标题行出现错误。
看起来编译器永远不会超越前向声明,而是使一切都完成的实际定义。

我的选项不多了,你有什么想法吗?

【问题讨论】:

  • 我很难关注你的帖子,我已经失去了#include "vectorstack.cpp"。为什么要包含源文件?请阅读minimal reproducible example 并尝试使您的示例更简洁
  • 搞错了,我的意思是VectorStack.hpp
  • 仍然是太多的代码。两个类和一个方法应该足以证明问题
  • 那么迭代器应该是返回栈顶的值,同时将其从栈中移除?这听起来根本不像一个迭代器,你只需要一个函数。
  • 另一方面,这里常用的方法是将VectorStackIterator 类放在VectorStack 类中。那么你就不需要转发声明任何东西了。

标签: c++ templates generics forward-declaration


【解决方案1】:

关注您的帖子有点困难。但总的来说,有一些事情要记住:

  • 按值存储的类成员要求在声明包含类时完整的类型可用,因为编译器需要知道对象应该占用多少内存。
  • 在声明包含类时,指针和引用成员不需要完整,因为大小始终只是指针的大小。
  • 在您开始使用相关对象时始终需要完整类型,因为编译器需要知道该类型应包含哪些成员变量和函数。
  • 如果您遇到无法解决“类型不完整”错误的情况,请仔细检查您的设计以确保其合理;您不希望(例如)两种类型循环包含彼此(按值,引用和指针也可以)。

也就是说,我认为处理这个问题的标准方法是:

class ClassB;
class ClassA {
    ClassB* or ClassB&
}
class ClassB {
    ClassA
}
ClassA::implementations // These two can happen in any order, since both ClassA and ClassB are complete at this point
ClassB::implementations

由于您的两个类都是模板化的,因此需要将实现放在头文件中,因此您可能需要小心构建文件的方式以强制执行这些部分的发生顺序。

【讨论】:

  • 在我的脑角写下这些经验法则,这样我下次遇到这个问题时就可以避免被否决。非常感谢!
  • @qreon 你没有因为缺乏知识而被否决,而是因为缺乏 mcve ;)
  • 也写下这个经验法则?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多