如何避免包含类实现文件? [英] How can I avoid including class implementation files?

查看:137
本文介绍了如何避免包含类实现文件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

而不是

  #includeMyClass.cpp
pre>

我想做

  #includeMyClass。 h

我在网上看过不是练习。

解决方案

简单分开编译



得到一些快速示例:

  struct ClassDeclaration; //'class'/'struct'的意思几乎相同的事情
struct ClassDefinition {}; //唯一的区别是默认的可访问性
//和成员的

void function_declaration();
void function_definition(){}

extern int global_object_declaration;
int global_object_definition;

template< class T> //不能用'struct'替换这个'class'
struct ClassTemplateDeclaration;
template< class T>
struct ClassTemplateDefinition {};

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



翻译单位 h3>

翻译单元(TU)是单个源文件(应为* .cpp 文件)



标题



包含守卫是一个黑客来解决缺乏一个真正的模块系统,使标头成为一种有限的模块;为此,包括同一个标题不止一次不得有不良影响。



包括保护工作通过使后续的#includes no-ops,第一包括。由于它们的性质有限,控制标题选项的宏在整个项目中应该是一致的(像< assert.h>之类的奇怪标题导致问题),并且所有#包含的公共标题都应该在任何命名空间,类等之外



查看我的包含保护命名建议,包括一个短程序生成包含警卫



声明



>类,函数对象模板可以声明任何地方,可以声明任何次数, em>必须在以任何方式引用它们之前声明。在一些奇怪的情况下,你可以声明类,因为你使用它们;

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



实际用途 [2] 模板(类模板和函数模板)只定义在头文件中,如果要使用单独的文件, sup> [3] 。



[1] 由于最多一次的限制,标头使用include guard

[2] 我不会在这里讨论其他可能性。

[3] 将它命名为 blahblah_detail.hpp blahblah_private.hpp 或类似的,如果您要记录它是非公开的。



指南



所以,虽然我确定上面的一切都是一个大泥球到目前为止,它不是一个页面上什么应该一些章节,所以使用它作为简要参考。然而,理解上述概念是重要的。使用这些方法,以下是一个简短的指南列表(但不是绝对规则):




  • 单个项目,例如C的* .h 和C ++的* .hpp

  • 从不< >包含不是标题的文件。

  • 始终命名实现文件(将要直接编译),如*

  • 使用可以自动编译源文件的构建系统 是规范的例子,但有很多选择。在简单的情况下保持简单。例如,make可以使用它的内置规则,甚至没有makefile。

  • 使用可以生成头依赖项的构建系统。有些编译器可以使用命令行开关(例如 -M )生成此类,因此您可以使用非常有用的系统



构建过程



(下面是回答你的问题了点点,但你最需要的上方,才能到达这里。)



当你构建,构建然后系统会经过几个步骤,其中本次讨论的重要的有:




  1. 编译每个实现文件作为TU,产生对象文件(* .o ,* .obj

    • 其他的,这就是为什么每个TU需要声明和定义


  2. 链接这些文件,用指定的库一起,到一个可执行<的/ li>

我建议您学习make的基本知识,因为它非常流行,易于理解,并且易于开始使用。然而,这是一个老系统的几个问题,你要切换到在一些点别的东西。



选择一个构建系统几乎是一个宗教的经验,像选择一个编辑器,除非你就会有更多的人(每个人都工作在同一个项目),并很可能会更受先例和惯例的约束工作。您可以使用它处理同一个细节为你的IDE,不过这次使用的全面构建系统,而不是没有真正的好处,你真的应该还是知道它的引擎盖下做的。



文件模板



example.hpp



  IFNDEF EXAMPLE_INCLUDE_GUARD_60497EBE580B4F5292059C8705848F75 
的#define EXAMPLE_INCLUDE_GUARD_60497EBE580B4F5292059C8705848F75
//该项目所有项目特定的宏前缀EXAMPLE_

&#包括LT; ostream的> // required header /modules/ libraries from the
#include< string> // stdlib,this project,and elsewhere
#include< vector>

namespace example {//这个项目的主命名空间
template< class T>
结构TemplateExample {//出于实用的目的,干脆把整个
无效F(){} //类的定义,以及在头$ B $(B T)数据的所有方法。
};

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



< h3> example.cpp

  #includeexample.hpp//包含特定于此实现的标题
//文件首先,帮助确保头包括它需要的任何东西(是
// independent)

#include< algorithm> //任何额外的不包括在标题
#include< iostream>

namespace example {
FooBar :: FooBar():v(42){} // define ctor

int main(std :: vector& :string> args){// define function
using namespace std; // use inside function scope,如果需要,总是好的
//但使用外部函数范围可能有问题
cout<< 现在做真正的工作... \\\
; // no std :: needed here
return 42;
}
} // example ::



main.cpp



  #include< iostream> 
#includeexample.hpp

int main(int argc,char const ** argv)try {
//在真正的main之前进行任何全局初始化
return example :: main(std :: vector< std :: string>(argv,argv + argc));
}
catch(std :: exception& e){
std :: cerr< [未捕获异常:< e.what()< ] \\\
;
return 1; //或EXIT_FAILURE等。
}
catch(...){
std :: cerr< [未知未捕获异常] \\\
;
return 1; //或EXIT_FAILURE等。
}


Instead of doing

#include "MyClass.cpp"

I would like to do

#include "MyClass.h"

I've read online that not doing so is considered bad practice.

解决方案

Separate compilation in a nutshell

First, let's get some quick examples out there:

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() {}

Translation Unit

A translation unit (TU) is a single source file (should be a *.cpp file) and all the files it includes, and they include, etc. In other words: the result of preprocessing a single file.

Headers

Include guards are a hack to work around lack of a real module system, making headers into a kind of limited module; to this end, including the same header more than once must not have an adverse affect.

Include guards work by making subsequent #includes no-ops, with the definitions available from the first include. Because of their limited nature, macros which control header options should be consistent throughout a project (oddball headers like <assert.h> cause problems) and all #includes of public headers should be outside of any namespace, class, etc., usually at the top of any file.

See my include guard naming advice, including a short program to generate include guards.

Declarations

Classes, functions, objects, and templates may be declared almost anywhere, may be declared any number of times, and must be declared before referring to them in any way. In a few weird cases, you can declare classes as you use them; won't cover that here.

Definitions

Classes may be defined at most once[1] per TU; this typically happens when you include a header for a particular class. Functions and objects must be defined once in exactly one TU; this typically happens when you implement them in a *.cpp file. However, inline functions, including implicitly inline functions inside class definitions, may be defined in multiple TUs, but the definitions must be identical.

For practical purposes[2], templates (both class templates and function templates) are defined only in headers, and if you want to use a separate file, then use another header[3].

[1] Because of the at-most-once restriction, headers use include guards to prevent multiple inclusion and thus multiple definition errors.
[2] I won't cover the other possibilities here.
[3] Name it blahblah_detail.hpp, blahblah_private.hpp, or similar if you want to document that it's non-public.

Guidelines

So, while I'm sure everything above is all a big ball of mud so far, it's less than a page on what should take up a few chapters, so use it as a brief reference. Understanding the concepts above, however, is important. Using those, here's a short list of guidelines (but not absolute rules):

  • Always name headers consistently in a single project, such as *.h for C and *.hpp for C++.
  • Never include a file which is not a header.
  • Always name implementation files (which are going to be directly compiled) consistently, such as *.c and *.cpp.
  • Use a build system which can compile your source files automatically. make is the canonical example, but there are many alternatives. Keep it simple in simple cases. For example, make can be used its built-in rules and even without a makefile.
  • Use a build system which can generate header dependencies. Some compilers can generate this with command-line switches, such as -M, so you can make a surprisingly useful system easily.

Build Process

(Here's the tiny bit that answers your question, but you need most of the above in order to get here.)

When you build, the build system will then go through several steps, of which the important ones for this discussion are:

  1. compile each implementation file as a TU, producing an object file (*.o, *.obj)
    • each is compiled independently of the others, which is why each TU needs declarations and definitions
  2. link those files, along with libraries specified, into a single executable

I recommend you learn the rudiments of make, as it is popular, well-understood, and easy to get started with. However, it's an old system with several problems, and you'll want to switch to something else at some point.

Choosing a build system is almost a religious experience, like choosing an editor, except you'll have to work with more people (everyone working on the same project) and will likely be much more constrained by precedent and convention. You can use an IDE which handles the same details for you, but this has no real benefit from using a comprehensive build system instead, and you really should still know what it's doing under the hood.

File Templates

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.
}

这篇关于如何避免包含类实现文件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆