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
。
- node.h
- node.hpp
- array_node_container.h
- array_node_container.hpp
- array.h
- array.hpp
- array_container.h
- array_container.hpp
- node.h
而单独跟踪
array.h
的依赖路径,也会产生类似的递归依赖:
- array.h
- array.hpp
- array_container.h
- array_container.hpp
- node.h
- node.hpp
- array_node_container.h
- array_node_container.hpp
- array.h
头文件展开
我们可以通过
gcc/clang
的
-E -P
命令来处理这两个
cpp
文件,来查看最后生成的头文件展开结果。
clang -E -P node.cpp > expand_node.txt
的结果太长,我就简单介绍一下最终展开结果中上述头文件正文内容的插入顺序:
- node.h
- array_node_container.h
- array.h
- array_container.h
- array_container.hpp
- array.hpp
- array_node_container.hpp
- node.hpp
- node.cpp
类似的
clang -E -P array.cpp > expand_array.txt
的结果如下:
- array.h
- array_container.h
- node.h
- array_node_container.h
- array_node_container.hpp
- node.hpp
- array_container.hpp
- array.hpp
- 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”