头文件互相包含解决_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.cppnode.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.harray.h是互相依赖的,按照常理展开的时候会递归展开,从而导致无限递归爆栈。阻止这个情况发生依赖于#pragma once这个声明, 从而起到了pythonimport的效果,引入一个头文件的时候会记录这个头文件已经被引入了,后续的引入检查这个标记,如果已经被引入则不做任何操作。除了#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”
  array_node_container.h(3): note: 参见“Array”的声明
1>node.cpp
  array_container.h(23): error C2027: 使用了未定义类型“Node”
  array_container.h(4): note: 参见“Node”的声明
1>正在生成代码...
1>已完成生成项目“test_recursive.vcxproj”的操作 - 失败。

求各位路过的行行好指导一下如何改展开hpp之后的文件,让项目编译通过吧,卡这快一个礼拜了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值