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

Dav*_*uve 3 c++ coding-style class include

而不是做

#include "MyClass.cpp"
Run Code Online (Sandbox Code Playgroud)

我想要做

#include "MyClass.h"
Run Code Online (Sandbox Code Playgroud)

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

小智 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() {}
Run Code Online (Sandbox Code Playgroud)

翻译单位

翻译单元(TU)是一个单一的源文件(应为一个**CPP*文件.)和所有文件,它包括,并且它们包括,等等.换言之:预处理的单个文件的结果.

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

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

请参阅我的include guard 命名建议,包括生成包含警卫的简短程序.

声明

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

定义

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

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

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

方针

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

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

构建过程

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

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

  1. 将每个实现文件编译为TU,生成目标文件(**.o*,**.obj*)
    • 每个都是独立编译的,这就是每个TU需要声明和定义的原因
  2. 将这些文件以及指定的库链接到单个可执行文件中

我建议你学习制作的基本知识,因为它很受欢迎,很容易理解,而且很容易上手.但是,它是一个有几个问题的旧系统,你需要在某个时候切换到别的东西.

选择一个构建系统几乎是一种宗教体验,比如选择一个编辑器,除了你必须与更多人(每个人在同一个项目上工作)合作,并且可能会受到先例和惯例的限制.你可以使用一个为你处理相同细节的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
Run Code Online (Sandbox Code Playgroud)

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::
Run Code Online (Sandbox Code Playgroud)

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.
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,无法解释比你更短的C++编译模型.不幸的是,因为它是非常重要的信息.来自我的+1以获得勇敢的尝试;) (2认同)