如何避免公共头中#define编译时间过长

How to avoid long compilation time for #define in common header

本文关键字:编译 时间 #define 何避免      更新时间:2023-10-16

我想知道是否有一种优雅的方法可以解决这个问题。假设有一个共同的标题,例如

// common.h
#ifndef COMMON_H
#define COMMON_H
#define ENABLE_SOMETHING
//#define ENABLE_SOMETHING_ELSE
#define ENABLE_WHATEVER
// many others
#endif

现在这个文件包含在100个其他头文件中,各种#define用于启用或禁用代码的某些部分,这些部分仅限于1-2个文件。

每次更改单个#define时,整个项目似乎都会重新构建(我正在开发Xcode 5.1(,这是有意义的,因为它必须在代码的各个部分进行替换,而且编译器无法先验地知道它在哪里使用。

我正试图找到一种更好的方法来管理它,以避免长的编译时间,因为这些定义确实更改了很多次。将每个定义拆分到相应的文件中可能是一种解决方案,但我希望以实用的方式将所有内容打包在一起。

所以我想知道是否有一种模式通常用于解决这个问题,我正在考虑使用

// common.h
class Enables
{
  static const bool feature;
};
// common..cpp
bool Enables::feature = false;

在编译优化的二进制文件时,这在语义上是否等效?(例如,false enables中的代码将完全消失(。

这里有两个不同的问题:

将每个定义拆分到相应的文件中可能是一种解决方案,但我希望以实用的方式将所有内容打包在一起。

这是你的第一个问题。如果我没有正确理解,如果你有多个功能区域,你就不想为每个功能区域都包含一个标题(但所有功能都只有一个标题(。

应用以下步骤:

  • do按功能将代码拆分为不同的标头;每个标头应包含(最多(单个#define FEATURESET启用的内容(并且完全不知道FEATURESET宏的存在(。

  • 确保每个标头只编译一次(在每个特性标头文件的开头添加#pragma once(

  • 添加一个方便的头文件,根据您定义的功能执行#if#ifdef,并根据需要包括功能文件:

    // parsers.h
    // this shouldn't be here: #pragma once
    #ifdef PARSEQUUX_SAFE
    #include <QuuxSafe.h>
    #elif defined PARSEQUUX_FAST
    #include <QuuxFast.h>
    #else
    #include <QuuxSafe.h>
    #endif
    // eventually configure static/global class factory here
    // see explanation below for mentions of class factory
    

客户代码:

#include <parsers.h> // use default Quux parser
#define PARSEQUUX_SAFE
#include <parsers.h> // use safe (but slower) Quux parser

所以我想知道是否有一种模式通常用于解决这个问题

这是你的第二个问题。

在C++中,通过特性启用功能的规范方法是定义特性API,包括基类、类工厂和对通用接口的编程。

// common.h
#pragma once
#include <Quux.h> // base Quux class
struct QuuxFactory
{
    enum QuuxType { Simple, Feathered };
    static std::unique_ptr<Quux> CreateQuux(int arg);
    static QuuxType type;
};
// common.cpp:
#include <common.h>
#include <SimpleQuux.h> // SimpleQuux: public Quux
#include <FeatheredQuux.h> // FeatheredQuux: public Quux
std::unique_ptr<Quux> QuuxFactory::CreateQuux(int arg)
{
    switch(type) {
    case Simple:
        return std::unique_ptr<Quux>{new SimpleQuux{arg}};
    case Feathered:
        return std::unique_ptr<Quux>{new FeatheredQuux{arg}};
    };
    // TODO: handle errors
}

客户代码:

// configure behavior:
QuuxFactory::type = QuuxFactory::FeatheredQuux;
// ...
auto quux = QuuxFactory::CreateQuux(10); // creates a FeatheredQuux in this case

这具有以下优点:

  • 它很简单,不使用宏

  • 它是可重复使用的

  • 它提供了足够的抽象层次

  • 它不使用宏(如在"完全"(

  • 假设的Quux功能的实际实现仅包含在一个文件中(作为实现细节,仅编译一次(。你可以在任何地方包含common.h,它根本不包括SimpleQoux.h和FeaturedQoux.h。

作为通用指南,您应该编写代码,这样就不需要运行宏。如果你这样做了,你会发现你想在上面添加的任何宏都是微不足道的。如果您从一开始就依赖宏来定义API,那么如果没有宏,代码将无法使用(或几乎无法使用(。

有一种方法可以拆分定义,但仍然使用一个中心配置头。

main_config.h(它不能有包含保护或#pragma once,因为如果在一个编译单元中多次包含main_config.h,会导致奇怪的结果(:

#ifdef USES_SOMETHING
#include "something_config.h"
#endif
#ifdef USES_WHATEVER
#include "whatever_config.h"
#endif

something_config.h(由于与main_config.h相同的原因,不得包含防护装置(:

#define ENABLE_SOMETHING

所有的源文件和头文件都将只包含#includemain_config.h,但在包含之前,它们必须声明它们将引用其中的哪个部分:

some_source.cpp

#define USES_SOMETHING
#include "main_config.h"

some_other_file.h

#define USES_WHATEVER
#include "main_config.h"