如何避免包含类实现文件?

时间:2010-01-10 17:47:55

标签: c++ class coding-style include

而不是做

#include "MyClass.cpp"

我想做

#include "MyClass.h"

我在网上看到这样做被认为是不好的做法。

7 个答案:

答案 0 :(得分:13)

简单地单独编译

首先,我们来看一些简单的例子:

struct ClassDeclaration;   // 'class' / 'struct' mean almost the same thing here
struct ClassDefinition {}; // the only difference is default accessibility
                           // of bases and members

void function_declaration();
void function_definition() {}

extern int global_object_declaration;
int global_object_definition;

template<class T>           // cannot replace this 'class' with 'struct'
struct ClassTemplateDeclaration;
template<class T>
struct ClassTemplateDefinition {};

template<class T>
void function_template_declaration();
template<class T>
void function_template_definition() {}

翻译单位

翻译单元(TU)是单个源文件(应该是** .cpp *文件)及其包含的所有文件,包括等等。换句话说:预处理单个文件的结果。

接头

包含防护是一种破解工作,缺乏真正的模块系统,使标头成为一种有限的模块;为此,不止一次包括相同的标题不得产生不利影响。

通过制作后续#includes no-ops来包含警卫工作,其中包含第一个包含的定义。由于它们的性质有限,控制标题选项的宏在整个项目中应该是一致的(奇怪的标题,如&lt; assert.h&gt;导致问题),所有#includes的公共标题应该在任何名称空间,类等之外,通常在任何文件的顶部。

查看我的包含警示naming advice,包括generate include guards的简短程序。

声明

函数对象模板几乎可以在任何地方声明,可以声明任意数字时间,必须在以任何方式引用它们之前声明。在一些奇怪的情况下,您可以在使用它们时声明类;这里不会涉及。

<强>解释

每个TU [1] 最多可以定义

;当您为特定类包含标头时,通常会发生这种情况。 函数对象必须在一个TU中定义一次;当您在** .cpp *文件中实现它们时,通常会发生这种情况。但是,内联函数,包括类定义中的隐式内联函数,可以在多个TU中定义,但定义必须相同。

出于实际目的 [2] templates (类模板和函数模板)仅在头文件中定义,如果要使用单独的文件,则使用另一个标题 [3]

[1] 由于最多一次的限制,标题使用包含防护来防止多次包含,从而防止多个定义错误。
[2] 我不会在这里讨论其他可能性 [3] 将其命名为 blahblah_detail.hpp blahblah_private.hpp ,或类似名称,如果您想记录它是非公开的。

准则

所以,虽然我确定上面的所有内容到目前为止都是一个很大的漏洞,但它不应该是一篇关于应该占用几章的页面,所以请将它作为一个简短的参考。但是,理解上述概念很重要。使用这些,这里是一个简短的指南列表(但不是绝对规则):

  • 始终在单个项目中始终名称标题,例如**。h *代表C和**。hpp *代表C ++。
  • 从不包含不是标题的文件。
  • 始终名称实施文件(将直接编译),例如** .c *和**。cpp *。
  • 使用构建系统,它可以自动编译源文件。 make 是规范的例子,但有很多选择。在简单的情况下保持简单。例如,make可以使用其内置规则,甚至没有makefile。
  • 使用可生成标头依赖关系的构建系统。有些编译器可以使用命令行开关生成它,例如 -M ,因此您可以轻松地生成surprisingly useful system

构建过程

(这是回答你问题的一小部分,但你需要上面的大部分才能到达这里。)

构建时,构建系统将经历几个步骤,其中重要的步骤是:

  1. 将每个实现文件编译为TU,生成目标文件(**。o *,**。obj *)
    • 每个都是独立编译,这就是每个TU需要声明和定义的原因
  2. 将这些文件以及指定的库链接到单个可执行文件
  3. 我建议你学习制作的基本知识,因为它很受欢迎,很容易理解,而且很容易上手。但是,它是一个有几个问题的旧系统,你需要在某些时候切换到别的东西。

    选择一个构建系统几乎是一种宗教体验,比如选择一个编辑器,除了你必须与更多的人合作(每个人都在同一个项目上工作),并且可能会受到先例和惯例的限制。你可以使用一个为你处理相同细节的IDE,但是使用全面的构建系统并没有真正的好处,你真的应该知道它在幕后做了什么。

    文件模板

    example.hpp

    #ifndef EXAMPLE_INCLUDE_GUARD_60497EBE580B4F5292059C8705848F75
    #define EXAMPLE_INCLUDE_GUARD_60497EBE580B4F5292059C8705848F75
    // all project-specific macros for this project are prefixed "EXAMPLE_"
    
    #include <ostream> // required headers/"modules"/libraries from the
    #include <string>  // stdlib, this project, and elsewhere
    #include <vector>
    
    namespace example { // main namespace for this project
    template<class T>
    struct TemplateExample { // for practical purposes, just put entire
      void f() {}            // definition of class and all methods in header
      T data;
    };
    
    struct FooBar {
      FooBar(); // declared
      int size() const { return v.size(); } // defined (& implicitly inline)
    private:
      std::vector<TemplateExample<int> > v;
    };
    
    int main(std::vector<std::string> args); // declared
    } // example::
    
    #endif
    

    example.cpp

    #include "example.hpp" // include the headers "specific to" this implementation
    // file first, helps make sure the header includes anything it needs (is
    // independent)
    
    #include <algorithm> // anything additional not included by the header
    #include <iostream>
    
    namespace example {
    FooBar::FooBar() : v(42) {} // define ctor
    
    int main(std::vector<std::string> args) { // define function
      using namespace std; // use inside function scope, if desired, is always okay
      // but using outside function scope can be problematic
      cout << "doing real work now...\n"; // no std:: needed here
      return 42;
    }
    } // example::
    

    的main.cpp

    #include <iostream>
    #include "example.hpp"
    
    int main(int argc, char const** argv) try {
      // do any global initialization before real main
      return example::main(std::vector<std::string>(argv, argv + argc));
    }
    catch (std::exception& e) {
      std::cerr << "[uncaught exception: " << e.what() << "]\n";
      return 1; // or EXIT_FAILURE, etc.
    }
    catch (...) {
      std::cerr << "[unknown uncaught exception]\n";
      return 1; // or EXIT_FAILURE, etc.
    }
    

答案 1 :(得分:3)

这称为separate compilation model。您将类声明包含在需要它们的每个模块中,但定义它们only once

答案 2 :(得分:2)

除了隐藏cpp文件中的实现细节(查看其他回复)之外,您还可以通过类前向声明​​来隐藏结构细节。

class FooPrivate;  

class Foo  
{  
  public:  
  // public stuff goes here  
  private:  
  FooPrivate *foo_private;  
};

表达式class FooPrivate表示FooPrivate完全定义在其他位置(最好在Foo实现所在的同一文件中,Foo之前的内容。这样就可以确保Foo(Private)的实现细节不会通过头文件公开。

答案 3 :(得分:2)

您不需要包含.c或.cpp文件 - 编译器将编译它们,无论它们是否包含在其他文件中。但是,如果其他文件不知道类/ methods / functions / global vars /包含在其中的任何内容,则.c / .cpp文件中的代码是无用的。这就是标题发挥作用的地方。在标题中,您只放置声明,例如:

//myfile.hpp
class MyClass {
    public:
        MyClass (void);
        void myMethod (void);
        static int myStaticVar;
    private:
        int myPrivateVar;
};

现在,所有#include“myfile.hpp”的.c / .cpp文件都能够创建MyClass的实例,在myStaticVar上运行并调用MyClass :: myMethod(),即使这里没有实际的实现!见

实现(实际代码)进入myfile.cpp,在那里你告诉编译器你的所有东西:

//myfile.cpp
int MyClass::myStaticVar = 0;

MyClass::MyClass (void) {
    myPrivateVar = 0;
}

void MyClass::myMethod (void) {
    myPrivateVar++;
}

你永远不会在任何地方包含这个文件,这绝对没有必要。

提示:创建一个main.hpp(或main.h,如果您愿意 - 没有区别)文件并将所有#include放在那里。然后每个.c / .cpp文件只需要有这一行:#include“main.hpp”。这足以访问您在整个项目中声明的所有类,方法等。)。

答案 4 :(得分:1)

您不应包含源文件(.c或.cpp)。相反,您应该包含包含声明的相应头文件(.h)。源文件需要单独编译并链接在一起以获得最终的可执行文件。

答案 5 :(得分:1)

应在编译器脚本中定义Cpp文件,以便将其编译为目标文件。

你使用什么ide? 我假设您正在使用gcc编译,所以这里是将两个.cpp文件编译成一个可执行文件的命令

gcc -o myclasses.out myclass.cpp myotherclass.cpp

您应该只使用#include来包含类定义,而不是实现

答案 6 :(得分:1)

当您从.h / .hpp中包含类声明时,您需要注意的一件事是确保它只被包含一次。如果你不这样做,你会得到一些可能是神秘的编译器错误,这些错误会让你陷入困境。

要执行此操作,您需要使用#define告诉编译器仅在#define不存在时才包含该文件。

例如(MyClass.h):

#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass 
{
// Memebers and methods
}
#endif
// End of file

即使您将类声明包含在许多不同的.cpp文件中,也可以保证您的类声明只被包含一次。