【问题标题】:How to declare/define interdependent templates in C++?如何在 C++ 中声明/定义相互依赖的模板?
【发布时间】:2018-01-06 06:14:24
【问题描述】:

通常在 C++ 中,当我需要类之间的相互依赖关系时,我会在头文件中使用前向声明,然后在每个 cpp 文件中包含这两个头文件。

但是,这种方法在使用模板时会中断。因为模板必须完全位于头文件中(不包括我将代码放入 cpp 并为每个支持的 T 枚举 template class A<T>; 的情况 - 这并不总是可行的,例如当 T 是 lambda 时)。

那么有没有办法在 C++ 中声明/定义相互依赖的模板?

代码示例

template<typename T> struct B;
template<typename T> struct A {
  void RunA(B<T> *pB) {
    // need to do something to B here
  }
};

template<typename T> struct B {
  void RunB(A<T> *pA) {
    // need to do something to A here
  }
};

如果我开始在RunA() 中对B 做某事,我想我会收到“缺少定义”错误,因为在编译RunA() 时只有B 的前向声明可用。

也许有一些技巧来组织头文件,例如通过将每个标头拆分为类定义和方法定义文件,然后以某种奇特的方式包含它们。或者也许可以通过第三/第四类来完成某些事情。但我无法想象如何具体做到这一点。

C++11/14/17 还可以(具体来说,它是 MSVC++2017,工具集 v141)。

【问题讨论】:

  • 不要列出编译器的年份,而是列出版本,因为VS15可以安装3个不同的编译器版本(之前的都只有一个)。
  • @SergeyB.,这解决了我给出的代码示例的特定问题。但是,我想要一个一般准则,例如“而不是使用 .h 和 .cpp 文件,您需要第三种类型的文件,例如 .inl ,它将包含函数定义”,然后解释如何管理包含。 ...
  • 也许您可以将成员方法设为函数模板并通过static_assert 强制类型,例如:template&lt;template&lt;typename&gt; class C&gt; void RunA(C&lt;T&gt; *pB { static_assert(std::is_same&lt;C&lt;T&gt;, B&lt;T&gt;&gt;::value, "!"); // ... }。这样,您只需要在调用点定义B。它对你有用吗?
  • @skypjack ,我更喜欢简单且可扩展的解决方案,例如 VTT 提出的单独标头。虽然我怀疑这是否是最简单的解决方案...... 4个标题似乎太多了。
  • @SergeRogatch 好吧,当第三个班级进入循环时,您可能已经回顾了您对 scalable 一词的含义。 :-)

标签: c++ templates compilation include c-preprocessor


【解决方案1】:

您可以将类的声明和定义分开。因此,您可以将模板类的声明和定义分开...

您可以将类方法的声明和定义分开。因此,您可以将模板类方法的声明和定义分开:

template<typename T> struct B;     // declaration

template<typename T> struct A {     // definition
  void RunA(B<T> *pB);  // declaration
};

template<typename T> struct B {     // definition
  void RunB(A<T> *pA);   // declaration
};

// definition
template<typename T>
void A<T>::RunA(B<T> *pB) {
    // need to do something to B here
  }

// definition    
template<typename T>
void B<T>::RunB(A<T> *pA) {
    // need to do something to A here
  }

【讨论】:

  • 这很清楚。但在实践中,将所有内容都放在一个文件中并不方便。问题是如何将这些声明/定义拆分为头文件,然后如何包含它们。
  • @SergeRogatch 与使用 .hpp 和 .cpp 的方式相同。规范是使用扩展名 .ipp 来定义模板函数。
  • ipp?有趣,在 15 年的实践中从未见过……通常是 .inc,有时是 .impl……但喜欢它。
【解决方案2】:

您可以使用细粒度的标头:

//  A.forward.hpp
template<typename T> struct A;

//  A.decl.hpp
#include "A.forward.hpp"
#include "B.forward.hpp"

template<typename T> struct A
{
    void RunA(B<T> *pB);
};

//  A.impl.hpp
#include "A.decl.hpp"
#include "B.hpp"

template<typename T> void A< T >::
RunA(B<T> *pB)
{
    // need to do something to B here
}

//  A.hpp // this one should be included by code using A
#include "A.decl.hpp"
#include "A.impl.hpp"

//  B.forward.hpp
template<typename T> struct B;

//  B.decl.hpp
#include "B.forward.hpp"
#include "A.forward.hpp"

template<typename T> struct B
{
    void RunB(A<T> *pA);
};

//  B.impl.hpp
#include "B.decl.hpp"
#include "A.hpp"

template<typename T> void B< T >::
RunB(A<T> *pA)
{
    // need to do something to A here
}

//  B.hpp // this one should be included by code using B
#include "B.decl.hpp"
#include "B.impl.hpp"

显然,所有这些标头还需要某种我在此处省略的标头保护。 重要提示.impl.hpp 标头被认为是内部的,不应被外部代码使用,而 .forward.hpp.decl.hpp.hpp 可以在任何地方使用。

这种方法确实引入了循环包含依赖项,但这不会导致问题:包含标头生成的代码结构确保代码部分按以下顺序包含:前向声明、类定义、方法定义。

【讨论】:

  • 这个设计中有一个循环包含:A.hpp -> A.impl.hpp -> B.hpp -> B.impl.hpp -> A.hpp
  • @SergeRogatch 是的,但是第二次不会包含 A.hpp,这不会导致问题。如果您仅包含 A.hpp、仅 B.hpp 或以任何顺序包含它们两者,它将正常工作。你应该自己试试。显然,所有这些标头还需要某种我在这里省略的标头保护。重要提示:.decl.hpp 和 .impl.hpp 头文件被认为是内部的,不应被外部代码使用,而 .forward.hpp 和 .hpp 可以在任何地方使用。
  • 这值得在你的回答中描述。另外,这是一些标准/最佳实践吗?或者你刚刚想出了这个解决方案?我想知道有多大的机会有更好的解决方案......
  • @SergeRogatch 好吧,至少在过去五年中,我一直在与SKU 一起练习这种方法。它真的很棒。通常我还会添加包含单元测试的.test.hpp,而通常不需要分离为.decl.hpp.impl.hpp
  • 我很高兴你有机会向世界展示你的方法! :) 。您能否描述一下在不分为.decl.hpp.impl.hpp 的情况下如何进行,或者您的意思是别的什么(例如,当没有相互依赖时)?另外,我很难想象这种方法如何扩展到 3、4 和更多相互依赖的类 - 你能澄清一下吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-03-26
  • 2011-01-25
  • 1970-01-01
  • 2013-09-03
  • 1970-01-01
  • 2013-05-29
  • 1970-01-01
相关资源
最近更新 更多