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

2024-02-19

而不是做

#include "MyClass.cpp"

我想要做

#include "MyClass.h"

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


简而言之单独编译

首先,让我们举一些简单的例子:

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

翻译单位

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

Headers

包含防护是解决缺乏真正的模块系统的一种技巧,将标头变成一种有限的模块;为此,多次包含相同的标头一定不会产生不利影响。

包含防护通过使后续 #includes 不执行任何操作来工作,并使用第一个包含中可用的定义。由于其有限的性质,控制标头选项的宏应该在整个项目中保持一致(像 这样的奇怪标头会导致问题),并且公共标头的所有 #include 都应该位于任何命名空间、类等之外,通常位于任何文件的顶部。

查看我的包含守卫命名建议 https://stackoverflow.com/questions/1744144/adding-ifndef-define-endif-breaks-the-compile/1744302#1744302,包括一个简短的程序生成包含守卫 http://bitbucket.org/kniht/scraps/src/tip/python/includeguard.py.

声明

Classes, 功能, objects, and 模板几乎可以在任何地方声明,可以声明任意次数,并且must在以任何方式引用它们之前进行声明。在一些奇怪的情况下,您可以在使用类时声明它们;这里不会介绍这个。

定义

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.

指南

因此,虽然我确信到目前为止以上所有内容都是一团泥,但它还不到一页,应该占据几章的内容,因此请将其用作简要参考。然而,理解上述概念很重要。使用这些,这里有一个简短的指南列表(但不是绝对规则):

  • Always在单个项目中一致地命名标头,例如 C 的 **.h* 和 C++ 的 **.hpp*。
  • Never包含一个不是标头的文件。
  • Always一致地命名实现文件(将直接编译),例如 **.c* 和 **.cpp*。
  • Use a 构建系统它可以自动编译您的源文件。make是典型的例子,但还有很多替代方案。在简单的情况下保持简单。例如,make 可以使用其内置规则,甚至无需 makefile。
  • 使用可以生成标头依赖项的构建系统。一些编译器可以使用命令行开关生成它,例如-M,所以你可以做一个非常有用的系统 http://bitbucket.org/kniht/scraps/src/tip/cpp/Makefile.common easily.

构建过程

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

当您构建时,构建系统将经历几个步骤,其中本次讨论的重要步骤是:

  1. compile each implementation file as a TU, producing an object file (**.o*, **.obj*)
    • 每个都被编译独立地其他的,这就是为什么每个 TU 都需要声明和定义
  2. 将这些文件与指定的库一起链接到单个可执行文件中

我建议您学习 make 的基础知识,因为它很流行、易于理解且易于入门。然而,它是一个存在一些问题的旧系统,您可能会在某个时候想要切换到其他系统。

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

文件模板

示例.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

示例.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.
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何避免包含类实现文件? 的相关文章

  • 何时使用 =default 使析构函数默认?

    尽管对构造函数使用 default 对我来说很清楚 即强制编译器在其他构造函数存在时创建默认构造函数 但我仍然无法理解这两种类型的析构函数之间的区别 那些使用 default 的 那些没有显式定义并由编译器自动生成的 我唯一想到的是 gro
  • FileStream 构造函数和默认缓冲区大小

    我们有一个使用 NET 4 用 C 编写的日志记录类 我想添加一个构造函数参数 该参数可以选择设置文件选项 WriteThrough http msdn microsoft com en us library system io fileo
  • 为什么在创建矩阵类时使用向量不好?

    对于我的矩阵类 我做了 template
  • 与 Qt 项目的静态链接

    我有一个在 Visual Studio 2010 Professional 中构建的 Qt 项目 但是 当我运行它 在调试或发布模式下 时 它会要求一些 Qt dll 如果我提供 dll 并将它们放入 System32 中 它就可以工作 但
  • ASP.Net Core 内容配置附件/内联

    我正在从 WebAPI 控制器返回一个文件 Content Disposition 标头值自动设置为 附件 例如 处置 附件 文件名 30956 pdf 文件名 UTF 8 30956 pdf 当它设置为附件时 浏览器将要求保存文件而不是打
  • vs2008 c#:Facebook.rest.api如何使用它来获取好友列表?

    如何在此基础上取得进一步的进步 获取好友列表的下一步是什么 string APIKey ConfigurationManager AppSettings API Key string APISecret ConfigurationManag
  • 从 WebBrowser 控件 C# 获取滚动值

    我试图在 WebBrowser 控件中获取网页的 Y 滚动索引 但无法访问内置滚动条的值 有任何想法吗 对于标准模式下的 IE 使用文档类型 正如你所说 scrollTop是的财产元素 而不是 HtmlDocument htmlDoc th
  • Visual Studio Code:如何配置 includePath 以获得更好的 IntelliSense 结果

    我是使用 Visual Studio Code 的完全初学者 我不知道我在做什么 我已经四处搜索 也许还不够 但我找不到像我这样的人如何配置的简单解释c cpp properties json每当我单击带有绿色波浪线下划线的行旁边的黄色灯泡
  • 如何从文本文件读取整数到数组

    这就是我想做的 我对此有些不满 但我希望你能容忍我 这对我来说是一个非常新的概念 1 在我的程序中 我希望创建一个包含 50 个整数的数组来保存来自文件的数据 我的程序必须获取用户的文档文件夹的路径 2 文件的名称为 grades txt
  • C++ php 和静态库

    我创建了一个library a 其中包含 cpp 和 h 文件 其中包含很多类 嵌套类和方法 我想在 php 示例中包含这个静态库并尝试使用它 我想提一下 我是 php 新手 我已经在 test cpp 文件中测试了我的 libray a
  • 检查 RoutedEvent 是否有任何处理程序

    我有一个自定义 Button 类 当单击它时 打开特定窗口 它总是执行相同的操作 我添加了一个可以在按钮的 XAML 中分配的 Click 事件 就像常规按钮一样 当它被单击时 我想执行 Click 事件处理程序 如果已分配 否则我想执行默
  • 将标量添加到特征矩阵(向量)

    我刚刚开始使用 Eigen 库 无法理解如何向所有矩阵成员添加标量值 假设我有一个矩阵 Eigen Matrix3Xf mtx Eigen Matrix3Xf Ones 3 4 mtx mtx 1 main cxx 104 13 error
  • 如何在c的case语句中使用省略号?

    CASE expr no commas ELLIPSIS expr no commas 我在c的语法规则中看到了这样的规则 但是当我尝试重现它时 int test float i switch i case 1 3 printf hi 它失
  • 我可以让 ungetc 取消阻止阻塞的 fgetc 调用吗?

    我想在收到 SIGUSR1 后使用 ungetc 将 A 字符重新填充到标准输入中 想象一下我有充分的理由这样做 调用 foo 时 stdin 中的阻塞读取不会被收到信号时的 ungetc 调用中断 虽然我没想到它会按原样工作 但我想知道是
  • 使用restsharp序列化对象并将其传递给WebApi而不是序列化列表

    我有一个看起来像的视图模型 public class StoreItemViewModel public Guid ItemId get set public List
  • cout 和字符串连接

    我刚刚复习了我的 C 我尝试这样做 include
  • 了解使用 Windows 本机 WPF 客户端进行 ADFS 登录

    我已经阅读了大量有关 ADFS 与 NodeJS Angular 或其他前端 Web 框架集成以及一般流程如何工作的文献 并通过 Auth0 Angular 起始代码构建了概念证明 但我不明白如何这可以与本机 WPF Windows 应用程
  • 您是否将信息添加到每个 .hpp/.cpp 文件的顶部? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 创建新的 C 头文件 源文件时 您会在顶部添加哪些信息 例如 您是否添加日期 您的姓名 文件描述等 您是否使用结构化格式来存储此信息 e g F
  • C++ Streambuf 方法可以抛出异常吗?

    我正在尝试找到一种方法来获取读取或写入流的字符数 即使存在错误并且读 写结束时间较短 该方法也是可靠的 我正在做这样的事情 return stream rdbuf gt sputn buffer buffer size 但如果streamb
  • 使用 QtWebEngine 将 C++ 对象暴露给 Qt 中的 Javascript

    使用 QtWebkit 可以通过以下方式将 C 对象公开给 JavascriptQWebFrame addToJavaScriptWindowObject如中所述https stackoverflow com a 20685002 5959

随机推荐