Forward-declaring 个相互需要的模板

Forward-declaring templates that need each other

uni 的一位教授希望我们使用 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<typename T> class 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;
};

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

这是我提出的最接近解决方案的尝试,但是编译器仍然抱怨 Incomplete types not allowed here 当我在 main函数:

#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;
}

如果我尝试 forward-declare 迭代器而不是向量堆栈,那么向量堆栈不再不完整,但迭代器不完整,我在 [=35= 的标题行出现错误]循环。
看起来编译器永远不会超越前向声明,直到完成所有内容的实际定义。

我运行没有选择,你有什么想法吗?

要跟上您的 post 有点难。但总的来说,有几点需要注意:

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

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

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

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