如何在其他标头中同时使用头文件和 cpp 文件?

How can I use both header files and cpp files within other headers?

提问人:ModernEraCaveman 提问时间:9/1/2023 最后编辑:ModernEraCaveman 更新时间:9/3/2023 访问量:121

问:

我正在 Visual Studio 中处理一个项目,我决定重新组织我的许多代码。不幸的是,我在链接不同的标头及其 cpp 文件时遇到了问题。

我有一个头文件,其中包含通过头文件链传递的库。我不知道这是如何工作的,但我能够让内容填充到所有标题中:

/* vkInstance.h */
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
/* code used other header files */
/* vkGraphicsPipeline.h */
#include "vkInstance.h" // gains access to <GLFW/glfw3.h> within "vkInstance.h"

#include "component.h" // gets access to <GLFW/glfw3.h> through #include "vkInstance.h"
/* other headers similar to component.h */

当组件标头的内容定义位于头文件中时,将定义该文件的元素。组件标头还具有一些宏定义:<GLFW/glfw3.h>

/* component.h */
#ifndef hComponent
#define hComponent

struct exampleStruct {
    exampleStruct(/* args */) {
        /* constructor code containing <GLFW/glfw3.h> items */
    }
    void exampleFunc(/* args */) {
        /* func code containing <GLFW/glfw3.h> items */
    }
}

#endif

虽然定义了上述项目的用法,但我想将许多定义从我的各种头文件移动到它们的 cpp 文件中,这就是我开始遇到问题的地方:<GLFW/glfw3.h>

/* component.h */
#ifndef hComponent
#define hComponent

struct exampleStruct {
    exampleStruct(/* args */);
    void exampleFunc(/* args */) {
        /* func code containing <GLFW/glfw3.h> items */
    }
}

#endif

/* component.cpp */
#include "component.h"

exampleStruct::exampleStruct(/* args */) {
    /* constructor code containing <GLFW/glfw3.h> items */
}

void exampleStruct::exampleFunc(/* args */) {
    /* func code containing <GLFW/glfw3.h> items */
}

虽然我可以访问头文件中的定义,但我无法访问 cpp 文件中的定义,并且程序无法构建。<GLFW/glfw3.h>

我觉得我对C++有很好的掌握,但我远非专家,所以如果我错过了一个明显的解决方案,请原谅我。如何避免此问题和/或更好地组织我的文件结构?

编辑: 很抱歉延迟响应,我已经能够弄清楚如何使用最小可重复的程序复制链接错误(LNK2005、LNK2001 和 LNK2019)。这是它的 github 页面,这里是程序内容的打印输出:

/* main.cpp */
#include "Header2.h"

int main() {

    library::object obj;
    library::object1 obj1;
    library::object2 obj2;

    return 0;
}
/* Header.h */
#pragma once // tried with and without, no difference
#include <iostream>

namespace library {
    template<typename T>
    void print(T input);

    void test()
    {// definition in the header rather than cpp causes LNK2005, 
     // but I'm not sure why it does this.
        std::cout << "This is a test." << std::endl;
    }

    struct object {
        static inline int i = 1;
        object();
        ~object() = default;
        // if I place 'void test()' and it's definition here,
        // then I don't get LNK2005
    };
}
/* Header.cpp */
#include "Header.h"

library::object::object()
{
    std::cout << "I am object!" << std::endl;
}

template<typename T>
void library::print(T input)
{
    std::cout << "This is the input: " << input << std::endl;
}
/* Header1.h */
#include "Header.h"

namespace library {
    struct object1 {
        object1();
        ~object1() = default;
    };
}
/* Header1.cpp */
#include "Header1.h"
#include <iostream>

library::object1::object1()
{
    std::cout << "I am object1!\n";
    library::print(object::i);
}
/* Header2.h */
#include "Header1.h"

namespace library {
    struct object2 {
        object2();
        ~object2() = default;
    };
}
/* Header2.cpp */
#include "Header2.h"
// including "Header.h" did not fix LNK2001 or LNK2019
#include <iostream>

library::object2::object2()
{
    std::cout << "I am object2!\n";
    library::print(object::i);
}
C++ 文件结构

评论

4赞 dbush 9/1/2023
您需要在使用它的每个文件中。#include <GLFW/glfw3.h>
3赞 HolyBlackCat 9/1/2023
每个标头都应该包括它需要的标头,仅此而已。要了解您现在看到的内容,请了解标头永远不会单独处理,而只是作为.cpp文件的一部分进行处理,其中以文本方式替换为标头的内容。#include
0赞 user207421 9/1/2023
“我想将许多定义从我的各种头文件移动到它们的 cpp 文件”:为什么?
2赞 Peter 9/1/2023
一般来说,你的目标是错误的。避免使用头文件使得必须手动将声明/定义从库复制到使用库的项目中的每个源文件中 - 这很容易出错(例如,如果库中的声明/定义发生更改,则很容易忘记更新每个源文件,这通常会导致难以追踪的未定义行为和错误)。头文件的主要用途是缓解此类问题。

答:

1赞 Remy Lebeau 9/1/2023 #1

在 cpp+h 示例中,您仍在头文件中定义,现在也在文件中定义。这是违反ODR(单一定义规则)的行为。您需要将头文件更改为简单地声明 ,就像使用构造函数一样。exampleFunc.cppexampleFuncexampleStruct

此外,由于该文件现在使用 ,因此需要该文件。除非该文件还需要该文件中的内容,否则请将其保留在那里。.cpp<GLFW/glfw3.h>#include.h#include

尝试更像这样的东西,这取决于实际需要什么:args

组件.h

#ifndef hComponent
#define hComponent

#include <GLFW/glfw3.h>

struct exampleStruct {
    exampleStruct(/* args */);
    void exampleFunc(/* args */);
};

#endif

component.cpp

#include "component.h"

exampleStruct::exampleStruct(/* args */) {
    /* constructor code containing <GLFW/glfw3.h> items */
}

void exampleStruct::exampleFunc(/* args */) {
    /* func code containing <GLFW/glfw3.h> items */
}

或者。。。

组件.h

#ifndef hComponent
#define hComponent

struct exampleStruct {
    exampleStruct(/* args */);
    void exampleFunc(/* args */);
};

#endif

component.cpp

#include "component.h"
#include <GLFW/glfw3.h>

exampleStruct::exampleStruct(/* args */) {
    /* constructor code containing <GLFW/glfw3.h> items */
}

void exampleStruct::exampleFunc(/* args */) {
    /* func code containing <GLFW/glfw3.h> items */
}

评论

0赞 ModernEraCaveman 9/2/2023
嘿@RemyLebaeu,我尝试遵循您的实现,但现在我只是得到了大量的链接错误(LNK2019、LNK2005 和 LNK2001)。
1赞 Remy Lebeau 9/2/2023
@ModernEraCaveman 那么你做错了其他我们看不见的事情。如果引用代码中链接器在代码或引用的库中找不到的内容,则会出现LNK2001LNK2019LNK2005是违反网上争议解决的结果。
0赞 ModernEraCaveman 9/3/2023
@RemyLebaeu 感谢您的反馈。我刚刚用一个最低限度的可重复程序更新了这篇文章,以解决链接错误。你能看一下吗?
1赞 Shreeyash Shrestha 9/2/2023 #2

我在这里可能是错的,但你写的代码肯定有一些明显的错误。这里的主要内容在这一部分:

/* vkGraphicsPipeline.h */
#include "vkInstance.h" // gains access to <GLFW/glfw3.h> within "vkInstance.h"

#include "component.h" // gets access to <GLFW/glfw3.h> through #include "vkInstance.h"
/* other headers similar to component.h */

您告诉您将在 vkInstance.h 中包含的 component.h/component.cpp 上访问 glfw 的部分是错误的。包括链条..不是那样工作的。他们就像..一系列标头包含,它传递包含的标头包含的包含。 您应该将 vkInstance.h 包含在 component.h 上,而不是单独的文件上,并期望代码神奇地将包含链接在一起。如果这是真的,那么整个标头链将无效。

试试这个,如果它不起作用,请告诉我:

/* component.h */

#ifndef hComponent
#define hComponent

#include"vkInstance.h"
//this makes sure that vkInstance is included on component.h ,
//which makes it able to include everything that was included
//in vkInstance.h on component.h

struct exampleStruct {
   exampleStruct(/* args */);
   void exampleFunc(/* args */) {
       /* func code containing <GLFW/glfw3.h> items */
   }
}

#endif

/* component.cpp */
#include "component.h"

exampleStruct::exampleStruct(/* args */) {
   /* constructor code containing <GLFW/glfw3.h> items */
}

void exampleStruct::exampleFunc(/* args */) {
   /* func code containing <GLFW/glfw3.h> items */
}

如果可以管理链,则无需一次又一次地包含它们

关于您将遇到的链接器错误,您可能需要尝试将 lib 文件链接到程序。GLFW 在其文件夹之间的某个地方有一个 glfw3.lib 文件。尝试链接它,看看它是否仍然给出错误

祝你好运!!