msvc与递归头文件依赖的bug

递归头文件依赖

互相依赖的对象

最近看代码的时候,见到如下骨骼精奇的代码,两个类互相引用,却并没有通过指针和引用的方式。

首先是 array.h ,里面 Array 引用 Node

#pragma once
#include <vector>
using namespace std;
class Node;
class Array
    public:
    Array()
    template <typename T>
    Array(vector<T>& _data);
    Node GetValue(size_t index) const;
#include "array.hpp"

然后是 node.h , 里面 Node 引用 Array :

#pragma once
#include <vector>
using namespace std;
class Array;
class Node
public:
    Node();
    template<typename T>
    Node(vector<T>& _data);
    Array GetArray() const;
#include "node.hpp"

按照常理,我们使用前向声明的类的时候,只能使用前向声明的类的指针或者引用。如果直接传递类对象,同一个翻译单元内找不到这个前向声明类的定义的时候,会编译不通过,提示无法使用不完整的类型。但是下面的代码是可以通过编译并运行的

#include "array.h"
#include <iostream>
int main(void)
    auto temp = Array();
    temp.GetValue(0);
    std::cout<<"wow"<<std::endl;

同樣的,下面的代码也是可以通过编译并运行的:

#include "node.h"
#include <iostream>
int main(void)
    auto temp = Node();
    temp.GetArray();
    std::cout<<"wow"<<std::endl;

互相include的头文件

能编译通过的核心就在这两个头文件末尾 include 的相关文件里,这里我把相关文件的详细内容都贴一下:

node.h 包含了 node.hpp

#pragma once
#include "array_node_container.h"
template<typename T>
Node::Node(vector<T>& data)

node.hpp 包含了 array_node_container.h

#pragma once
class Array;
template<typename T>
class ArrayNodeContainer
public:
    ArrayNodeContainer()
    Array GetArray() const;
#include "array_node_container.hpp"

array_node_container.hpp 包含了 array.h

#pragma once
#include "array.h"
template<typename T>
Array ArrayNodeContainer<T>::GetArray() const
    return Array();

array.h 依赖于 array.hpp , 而 array.hpp 依赖于 array_container.h

#pragma once
#include "array_container.h"
template<typename T>
Array::Array(vector<T>& _in_data)

array_container.h 依赖于 array_container.hpp

#pragma once
class Node;
template<typename T>
class ArrayContainer




    

public:
    ArrayContainer(vector<T>& a);
    Node GetValue(size_t index);
    vector<T> _Array;
#include "array_container.hpp"

array_container.hpp 依赖于 node.h

#pragma once
#include "node.h"
template <typename T>
ArrayContainer<T>::ArrayContainer(vector<T>& a)
    _Array = a;
template <typename T>
Node ArrayContainer<T>::GetValue(size_t index)
    return {_Array[index]};

上述的 hpp 文件都是用来定义类内部的模板函数,实现了一定程度上的声明与定义相分离原则。

另外还有两个简单的 cpp 文件, array.cpp node.cpp ,基本就是简单实现一下非模板接口。

array.cpp

#include "array.h"
Node Array::GetValue(size_t index) const
    return Node();

node.cpp

#include "node.h"
Node::Node()
Array Node::GetArray() const
    return Array();

至此我们跟踪 node.h 的所有依赖头文件,最终会依赖到自身 node.h

  1. node.h
  2. node.hpp
  3. array_node_container.h
  4. array_node_container.hpp
  5. array.h
  6. array.hpp
  7. array_container.h
  8. array_container.hpp
  9. node.h

而单独跟踪 array.h 的依赖路径,也会产生类似的递归依赖:

  1. array.h
  2. array.hpp
  3. array_container.h
  4. array_container.hpp
  5. node.h
  6. node.hpp
  7. array_node_container.h
  8. array_node_container.hpp
  9. array.h

头文件展开

我们可以通过 gcc/clang -E -P 命令来处理这两个 cpp 文件,来查看最后生成的头文件展开结果。

clang -E -P node.cpp > expand_node.txt 的结果太长,我就简单介绍一下最终展开结果中上述头文件正文内容的插入顺序:

  1. node.h
  2. array_node_container.h
  3. array.h
  4. array_container.h
  5. array_container.hpp
  6. array.hpp
  7. array_node_container.hpp
  8. node.hpp
  9. node.cpp

类似的 clang -E -P array.cpp > expand_array.txt 的结果如下:

  1. array.h
  2. array_container.h
  3. node.h
  4. array_node_container.h
  5. array_node_container.hpp
  6. node.hpp
  7. array_container.hpp
  8. array.hpp
  9. array.cpp

之前我们看到了 node.h array.h 是互相依赖的,按照常理展开的时候会递归展开,从而导致无限递归爆栈。阻止这个情况发生依赖于 #pragma once 这个声明, 从而起到了 python import 的效果,引入一个头文件的时候会记录这个头文件已经被引入了,后续的引入检查这个标记,如果已经被引入则不做任何操作。除了 #pragma once 之外,等价的还有形式为 #ifndef __H_ARRAY_H__ header guard

手工展开hpp文件

之前的代码看上去很完美,用极其诡异的方式构造了互相引用。个人觉得里面的那个 hpp 文件比较多余,因此动歪脑筋想手动展开所有的 hpp 文件到 h 文件里面,结果如下。

array.h

#pragma once
#include <vector>
#include "array_container.h"
using namespace std;
class Node;
class Array
    public:
    Array()
    template <typename T>
    Array(vector<T>& _data);
    Node GetValue(size_t index) const;
template<typename T>
Array::Array(vector<T>& _in_data)

array_container.h

#pragma once
#include <vector>
#include "node.h"
class Node;
using namespace std;
template<typename T>
class ArrayContainer
public:
    ArrayContainer(vector<T>& a);
    Node GetValue(size_t index);
    vector<T> _Array;
template <typename T>
ArrayContainer<T>::ArrayContainer(vector<T>& a)
    _Array = a;
template <typename T>
Node ArrayContainer<T>::GetValue(size_t index)
    return { _Array[index] };

array_node_container.h

#pragma once
#include "array.h"
class Array;
template<typename T>
class ArrayNodeContainer
public:
    ArrayNodeContainer()
    Array GetArray() const;
template<typename T>
Array ArrayNodeContainer<T>::GetArray() const
    return Array();

node.h

#pragma once
#include <vector>
#include "array_node_container.h"
using namespace std;
class Array;
class Node
public:
    Node();
    template<typename T>
    Node(vector<T>& _data);
    Array GetArray() const;
template<typename T>
Node::Node(vector<T>& data)

其他的 cpp 文件都不变,执行 clang ./main.cpp ./array.cpp ./node.cpp ,是可以正常生成可执行文件并运行的。

可是 msvc 他不同意啊。。。。。。。

1>array.cpp
  array_node_container.h(17): error C2027: 使用了未定义类型“Array”
  array_node_container.h(3): note: 参见“Array”的声明
  array_node_container.h(18): error C2027: 使用了未定义类型“Array”
  array_node_container.h(3): note: 参见“Array”的声明
1>main.cpp
  array_node_container.h(17): error C2027: 使用了未定义类型“Array”
  array_node_container.h(3): note: 参见“Array”的声明
  array_node_container.h(18): error C2027: 使用了未定义类型“Array”