【问题标题】:Why do C++ templates let me circumvent incomplete types (forward declarations)?为什么 C++ 模板可以让我规避不完整的类型(前向声明)?
【发布时间】:2026-01-13 21:35:02
【问题描述】:

我尝试了以下简单程序的三个迭代。这是编写容器和迭代器对类的高度简化的尝试,但我遇到了不完整类型(前向声明)的问题。我发现一旦我将所有内容都模板化,这实际上是可能的——但前提是我实际使用了模板参数! (我通过查看Google sparsetable code 意识到这一点。)

任何提示解释为什么第二个有效而第三个无效? (我知道为什么第一个不起作用 - 编译器需要知道容器的内存布局。)

提前致谢。

// This doesn't work: invalid use of incomplete type.
#if 0
struct container;
struct iter {
  container &c;
  int *p;
  iter(container &c) : c(c), p(&c.value()) {}
};
struct container {
  int x;
  int &value() { return x; }
  iter begin() { return iter(*this); }
};
int main() {
  container c;
  c.begin();
  return 0;
}
#endif

// This *does* work.
template<typename T> struct container;
template<typename T> struct iter {
  container<T> &c;
  T *p;
  iter(container<T> &c) : c(c), p(&c.value()) {}
};
template<typename T> struct container {
  T x;
  T &value() { return x; }
  iter<T> begin() { return iter<T>(*this); }
};
int main() {
  container<int> c;
  c.begin();
  return 0;
};

// This doesn't work either.
#if 0
template<typename T> struct container;
template<typename T> struct iter {
  container<int> &c;
  int *p;
  iter(container<int> &c) : c(c), p(&c.value()) {}
};
template<typename T> struct container {
  int x;
  int &value() { return x; }
  iter<int> begin() { return iter<int>(*this); }
};
int main() {
  container<int> c;
  c.begin();
  return 0;
}
#endif

【问题讨论】:

  • 您能否更具体地说明“不起作用”的实际含义?它是否无法编译,如果是这样,消息是什么?它是否无法正常运行,如果是,它做了什么你没想到的?
  • 编译失败,我指定的消息是:“不完整类型的无效使用。”

标签: c++ templates forward-declaration


【解决方案1】:

第一个需要container 的定义,因为您正在执行复制操作。如果你在container 的定义之后定义iter 的构造函数,你会没事的。所以:

struct container;
struct iter {
  container &c;
  int *p;
  iter(container &c);
};

struct container {
  int x;
  int &value() { return x; }
  iter begin() { return iter(*this); }
};

iter::iter(container &c) : c(c), p(&c.value()) {}

int main() {
  container c;
  c.begin();
  return 0;
}

第二个示例有效,因为在您实际在 main 函数中实例化一个类之前,没有类。到那时,所有类型都已定义。尝试在 main 之后移动任何 itercontainer 模板定义,你会遇到错误。

第三个例子是int 的特化,或者看起来是这样。这应该可以编译,因为没有使用 iter 的模板参数。你的专业化语法有点偏离。但是,没有合适的构造函数,所以你只会得到x 的垃圾。此外,迭代器可以通过指针很好地建模。传递this 的值不会有太大帮助。序列通常需要迭代器,而不是单个对象。不过,没有什么可以阻止您构建一个。

而且你不需要在函数体后面加上;

【讨论】:

  • 我的两分钱 - 你提到“传递这个价值”,更多细节可能会有所帮助。如果您返回/传递*this,则您有一个参考,+/- 推断的限定符。如果你返回this,你有一个指向当前对象的原始指针,你必须担心对象的生命周期。如果您需要在上下文之间传递指针,这主要适用于动态内存管理,最佳实践是智能(唯一/共享)指针或类似的 RAII 类型。 (您仍然可以传递this,但最好只用于阻塞函数。)有什么要添加/更正的吗?
【解决方案2】:

你可以在没有模板的情况下通过在定义容器之后定义 iter::iter() 来做到这一点:

struct container;

struct iter {
  container &c;
  int *p;
  iter(container &c);
};

struct container {
  int x;
  int &value() { return x; }
  iter begin() { return iter(*this); }
};

iter::iter(container &c)
    : c(c), p(&c.value()) {}

int main() {
  container c;
  c.begin();
  return 0;
}

模板版本有效,因为当您实例化模板时,两个类都已完全定义。

【讨论】:

    【解决方案3】:

    在第一种情况下,您试图在定义类之前访问 Container 类的成员函数,因此这是行不通的。

    在第二种情况下,模板在第一次与特定类型一起使用时被实例化。此时,Container 类已在 main 中定义,因此可以编译。

    第三种情况是循环引用。 container用iter,iter用container,所以不能用。

    【讨论】: