mcpField
H
header-optimization

header-optimization

github.com/openharmonyinsight开发3582 次安装11 周前更新
代码仓库

关于 header-optimization

header-optimization 是针对 ace_engine 的 C++ 头文件优化 skill,供维护代码和分析编译开销的开发者使用。它通过移出较长实现、精简依赖和使用前置声明来降低头文件耦合,并包含仅分析模式。

主要功能

  1. 01将较长的内联实现移至 CPP
  2. 02移除未使用的头文件依赖
  3. 03以适用的前置声明替代包含
  4. 04按需拆分常量和枚举头文件
  5. 05支持只分析并给出修改建议

适用场景

  1. 01降低特定头文件的编译开销
  2. 02检查 test_header.cpp 的依赖分析结果
  3. 03在不修改文件的情况下制定优化方案

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/openharmonyinsight/openharmony-skills --skill header-optimization

安装后在 Claude Code 中输入/header-optimization确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

README.md
examples/
README.md
examples/before-after/
forward_declaration_example.md
inline_extraction_example.md
references/
case-click_event-forward-declaration.md
case-drag-drop-forward-declaration.md
case-drag-event-include-reduction.md
case-split-enums.md
forward-declaration.md
patterns.md
pimp-guide.md
scripts/
analyze-includes.sh
extract-includes.py

详细介绍

ACE Engine 的头文件优化

通过系统性地减少依赖和重构实现,提升 ace_engine 中 C++ 头文件的编译效率。本技能侧重于尽量减少头文件依赖并将实现移出头文件,从而缩短编译时间、降低内存占用。

何时使用此技能

在以下情况调用此技能:

  • 优化特定头文件的编译效率
  • 使用 test_header.cpp 分析头文件依赖
  • 减少 ace_engine 中的头文件依赖
  • 分析特定头文件的编译性能
  • 用户指定“只分析不进行修改”

关键规则

优化时必须遵守以下限制:

  1. 保留业务逻辑——只修改结构,不改动功能
  2. 尽量减少新建文件——直接编辑当前头文件/cpp 文件;仅在拆分头文件时新建文件
  3. 限制范围——只执行关键步骤中定义的工作,不扩大修改范围
  4. 独立编译——使用 compile-analysis 技能验证单个文件的编译情况,绝不进行完整构建
  5. test_header.cpp 只读——绝不修改 test_header.cpp;它仅用于统计依赖

优化工作流

步骤 1:分析输入和模式

确定优化目标和模式:

  • 如果输入是头文件(.h):直接优化该头文件
  • 如果输入是 test_header.cpp:从其内容中提取头文件并优化该头文件
  • 检查用户是否指定了“只分析不进行修改”(仅分析模式)

在仅分析模式下:

  • 执行所有分析步骤
  • 生成优化建议
  • 不要修改任何文件
  • 呈现分析结果和建议的修改,供用户审批
步骤 2:将内联实现移至 CPP 文件

目标:将所有超过 3 行的方法实现从头文件移至 cpp 文件。

流程:

  1. 读取目标头文件
  2. 找出所有实现超过 3 行的方法(包括静态方法)
  3. 如果对应的 cpp 文件不存在,则创建该文件
  4. 将方法实现移至 cpp 文件
  5. 头文件中仅保留声明
  6. 模板方法请参阅步骤 6

3 行的判定标准:

// Less than 3 lines - Keep in header
int GetValue() const { return value_; }

// 3 lines or more - Move to cpp
void ProcessData() {
    auto result = Calculate();
    Validate(result);
    cache_.Store(result);
}

例外情况:简单的 getter/setter 通常只有一行,为保证性能,一般保留在头文件中。

步骤 3:移除不必要的头文件包含

目标:消除未使用的头文件依赖。

流程:

  1. 列出头文件中的所有 #include 指令
  2. 对每个被包含的头文件,确认是否实际使用:
    • 在文件中搜索来自该头文件的类型
    • 检查成员变量、函数参数或返回类型是否使用这些类型
    • 验证基类继承关系
  3. 移除没有实际用途的包含
  4. 在分析报告中说明移除原因

示例:

// Before
#include "base/memory/ace_type.h"
#include "core/components/common/layout.h"
#include "core/pipeline/base/element.h"  // Not used - REMOVE

// After
#include "base/memory/ace_type.h"
#include "core/components/common/layout.h"
步骤 4:将包含转换为前向声明

目标:在可行的情况下,用前向声明替换完整的头文件包含。

流程:

  1. 分析每个剩余的头文件包含实际需要提供的内容:
    • 仅需要类型名称(指针、引用、函数参数)
    • 需要类型定义(必须提供完整的类定义)
    • 静态常量或枚举
  2. 仅需要类型名称时,改用前向声明:
    // Before
    #include "core/components_ng/base/frame_node.h"
    
    // After
    namespace OHOS::Ace::NG {
    class FrameNode;
    }  // namespace OHOS::Ace::NG
    
  3. 仅在以下情况保留完整包含:
    • 继承该类
    • 类包含该类型的成员变量
    • 模板实例化需要完整定义
    • 访问静态常量或内联方法

前向声明模板:

namespace OHOS::Ace {
namespace NG {
class FrameNode;
class UINode;
}  // namespace NG
}  // namespace OHOS
步骤 4.1:高级前向声明场景

重要:本节包含基于 ace_engine 实际优化案例的高级前向声明模式。

场景 1:智能指针模板参数(RefPtr/WeakPtr)

适用前向声明的情况:

  • 类型仅用作 RefPtr<T> 或 WeakPtr<T> 的模板参数
  • 类型用智能指针作为成员变量
  • 类型用智能指针作为函数参数或返回值

规则:智能指针模板(RefPtr、WeakPtr、std::unique_ptr、std::shared_ptr)在头文件中不要求完整类型定义。

示例——优化前:

// click_event.h (BEFORE)
#include "core/components_ng/gestures/recognizers/click_recognizer.h"  // ❌ Full include

class ClickEventActuator {
private:
    RefPtr<ClickRecognizer> clickRecognizer_;  // Only needs forward declaration

    const RefPtr<ClickRecognizer>& GetClickRecognizer();  // Also OK with forward decl
};

示例——优化后:

// click_event.h (AFTER)
// Removed: #include "click_recognizer.h"
namespace OHOS::Ace::NG {
class ClickRecognizer;  // ✅ Forward declaration only
}

class ClickEventActuator {
private:
    RefPtr<ClickRecognizer> clickRecognizer_;  // Works with forward decl
};

// click_event.cpp
#include "core/components_ng/gestures/recognizers/click_recognizer.h"  // Full include here

const RefPtr<ClickRecognizer>& ClickEventActuator::GetClickRecognizer() {
    if (!clickRecognizer_) {
        clickRecognizer_ = MakeRefPtr<ClickRecognizer>();  // Needs full definition
    }
    return clickRecognizer_;
}

要点:

  • ✅ 使用 RefPtr<T> 的成员变量可以使用前向声明
  • ✅ 返回类型 const RefPtr<T>& 可以使用前向声明
  • ❌ 实际实例化 MakeRefPtr<T>() 需要在 .cpp 中提供完整定义

验证:编译头文件和 cpp,确保没有未定义类型错误。


场景 2:跨命名空间前向声明

适用情况:一个命名空间中的类型需要用于另一个命名空间的头文件中。

模式:在适当的命名空间作用域中添加前向声明。

示例——gesture_event_hub.h 需要 ClickInfo:

// click_event.h
#include "base/memory/ace_type.h"
#include "core/components_ng/event/gesture_event_actuator.h"
#include "ui/gestures/gesture_event.h"         // Provides GestureEvent, GestureEventFunc
#include "core/components_ng/event/target_component.h"  // Provides GestureJudgeFunc

// Cross-namespace forward declaration for gesture_event_hub.h
namespace OHOS::Ace {
class ClickInfo;  // Type defined in OHOS::Ace, needed by gesture_event_hub.h
}

namespace OHOS::Ace::NG {
class GestureEventHub;   // Forward declaration in target namespace
class ClickRecognizer;   // Forward declaration in target namespace
}

class ClickEventActuator : public GestureEventActuator {
    // ... implementation
};

原理:

  • 通过 frame_node.h → click_event.h 链接包含 gesture_event_hub.h 时
  • ClickInfo 的前向声明已经可用
  • 避免循环依赖
  • 减少命名空间之间的耦合

场景 3:用精确包含替换间接依赖

问题:移除较重的包含(例如 click_recognizer.h)会导致编译失败,因为其他类型是通过间接包含引入的。

解决方案:找出并直接包含必要的类型定义头文件。

示例——移除 click_recognizer.h:

// BEFORE: Heavy indirect dependencies
#include "core/components_ng/gestures/recognizers/click_recognizer.h"
// This indirectly brought in:
// - tap_gesture.h (GestureEventFunc)
// - gesture_recognizer.h
// - multi_fingers_recognizer.h
// - And many more...

// AFTER: Precise includes
#include "ui/gestures/gesture_event.h"         // Provides GestureEvent, GestureEventFunc
#include "core/components_ng/event/target_component.h"  // Provides GestureJudgeFunc

// Forward declarations
namespace OHOS::Ace::NG {
class ClickRecognizer;  // Only name needed for RefPtr<ClickRecognizer>
}

分析流程:

  1. 搜索头文件中的类型用法(例如 GestureEventFunc、GestureJudgeFunc)
  2. 使用 grep/搜索工具查找这些类型的定义位置
  3. 直接包含定义该类型的头文件
  4. 对 RefPtr/WeakPtr 类型,用前向声明替换较重的包含

场景 4:前向声明的完整判断矩阵
用法模式可以使用前向声明?需要完整包含?
T* 成员变量✅ 是❌ 否
T& 参数/返回值✅ 是❌ 否
RefPtr<T> 成员✅ 是❌ 否
RefPtr<T>& 返回值✅ 是❌ 否
WeakPtr<T> 成员✅ 是❌ 否
std::unique_ptr<T>✅ 是❌ 否
std::shared_ptr<T>✅ 是❌ 否
std::vector<T>❌ 否*✅ 是
std::vector<T*>✅ 是❌ 否
类继承❌ 否✅ 是
T 成员变量❌ 否✅ 是
T 值参数❌ 否*✅ 是
模板实例化❌ 否✅ 是
访问静态成员❌ 否✅ 是
访问内联方法❌ 否✅ 是

* 使用 extern 模板时存在例外


常见问题与解决方案

问题 1:移除包含会导致依赖该头文件的头文件出错

症状:

error: unknown type name 'ClickInfo' in gesture_event_hub.h

根本原因:gesture_event_hub.h 通过 click_recognizer.h 间接获取了 ClickInfo

解决方案:在适当的命名空间中添加前向声明

namespace OHOS::Ace {
class ClickInfo;  // Forward declaration for gesture_event_hub.h
}

问题 2:移除包含后缺少类型定义

症状:

error: unknown type name 'GestureEventFunc'
error: unknown type name 'GestureJudgeFunc'

根本原因:这些类型是通过 click_recognizer.h 间接包含的

解决方案:直接包含定义这些类型的头文件

#include "ui/gestures/gesture_event.h"         // GestureEvent, GestureEventFunc
#include "core/components_ng/event/target_component.h"  // GestureJudgeFunc

问题 3:前向声明中的命名空间不匹配

症状:

error: no type named 'ClickInfo' in namespace 'OHOS::Ace::NG'

根本原因:ClickInfo 位于 OHOS::Ace,而不是 OHOS::Ace::NG

解决方案:在前向声明中使用正确的命名空间

namespace OHOS::Ace {        // Correct namespace
class ClickInfo;
}

namespace OHOS::Ace::NG {    // Different namespace
class ClickRecognizer;
}

真实案例研究:click_event.h 优化

优化前:

// 214 lines, 6 includes
#include <list>
#include "base/memory/ace_type.h"
#include "base/memory/referenced.h"              // Redundant
#include "base/utils/noncopyable.h"
#include "core/components_ng/event/gesture_event_actuator.h"
#include "core/components_ng/gestures/recognizers/click_recognizer.h"  // Heavy include

namespace OHOS::Ace::NG {
class GestureEventHub;  // Only 1 forward declaration
}

// 15 inline method implementations

优化后:

// 139 lines (-35%), 5 includes (-16.7%)
#include <list>
#include "base/memory/ace_type.h"
#include "base/utils/noncopyable.h"
#include "core/components_ng/event/gesture_event_actuator.h"
#include "ui/gestures/gesture_event.h"         // Precise include
#include "core/components_ng/event/target_component.h"  // Precise include

namespace OHOS::Ace {
class ClickInfo;  // Cross-namespace forward declaration
}

namespace OHOS::Ace::NG {
class GestureEventHub;    // Existing forward declaration
class ClickRecognizer;    // ✨ New forward declaration (RefPtr<T> optimization)
}

// 5 inline method implementations (moved 12 to cpp)

结果:

  • ✅ 头文件从 214 行减少到 139 行(-35.0%)
  • ✅ include 从 6 个减少到 5 个(-16.7%)
  • ✅ 前向声明从 1 个增加到 3 个(+200%)
  • ✅ 内联实现从 15 个减少到 5 个(-66.7%)
  • ✅ 已成功验证编译
  • ✅ 已验证 test_header.cpp 的间接依赖

应用的关键技巧:

  1. 将 #include "click_recognizer.h" 替换为前向声明
  2. 为类型定义添加精确的 include(GestureEventFunc、GestureJudgeFunc)
  3. 为 ClickInfo 添加跨命名空间的前向声明
  4. 将 12 个内联实现移至 cpp 文件

步骤 5:拆分常量和枚举的头文件

目标:将常用常量或枚举提取到单独的头文件中,以降低耦合度。

适用情况:

  • 原始头文件仅因少量常量或枚举值而被包含
  • 多个文件依赖这些常量,但不依赖完整头文件
  • 拆分能带来可衡量的编译收益

步骤:

  1. 找出其他文件所依赖的常量或枚举
  2. 创建新的头文件(例如,original_types.h、original_constants.h)
  3. 将提取的定义移至新头文件
  4. 更新原始头文件以包含拆分后的头文件
  5. 更新依赖文件,使其仅包含较小的头文件

示例:

// original.h (before)
#pragma once
namespace Constants {
    constexpr int MAX_VALUE = 100;
    enum class Type { A, B, C };
}

class BigImplementation {
    // ... hundreds of lines
};

// original_types.h (after split)
#pragma once
namespace Constants {
    constexpr int MAX_VALUE = 100;
    enum class Type { A, B, C };
}

// original.h (after split)
#pragma once
#include "original_types.h"
class BigImplementation {
    // ... implementation
};

验证:只有在拆分能带来明显的编译收益时才进行拆分。使用 compile-analysis skill 测量拆分前后的差异。

步骤 6:应用 PIMPL 模式(适用时)

目标:隐藏实现细节,以减少编译依赖。

适用情况:

  • 头文件因私有成员而存在许多繁重的依赖
  • 公共 API 稳定,但实现经常变化
  • 减少头文件依赖能显著缩短编译时间

步骤:

  1. 创建一个 Impl 类(在头文件中前向声明)
  2. 将私有成员和繁重的依赖移至 cpp 文件中的 Impl 类
  3. 将私有成员替换为 std::unique_ptr
  4. 更新构造函数和析构函数,以管理 Impl 的生命周期

PIMPL 模板:

// header.h
#pragma once
#include <memory>
namespace OHOS::Ace {
class MyClass {
public:
    MyClass();
    ~MyClass();

    // Public interface
    void PublicMethod();

private:
    class Impl;
    std::unique_ptr<Impl> impl_;
};

// cpp.cpp
#include "header.h"
#include "heavy_dependency.h"

class MyClass::Impl {
public:
    HeavyDependency dep_;
    // Private implementation details
};

MyClass::MyClass() : impl_(std::make_unique<Impl>()) {}
MyClass::~MyClass() = default;

void MyClass::PublicMethod() {
    impl_->dep_.DoSomething();
}

注意:PIMPL 会带来运行时开销(额外分配和间接访问)。只有当编译收益足以抵消成本时才使用。

步骤 7:优化模板方法

目标:通过显式实例化降低模板编译开销。

步骤:

  1. 找出头文件中的模板方法
  2. 检查是否可以将模板转换为带显式实例化的普通方法
  3. 将模板实现移至 cpp 文件
  4. 为常用类型添加显式实例化声明

示例:

// header.h (before)
template<typename T>
void Process(T value) {
    // Implementation
}

// header.h (after)
template<typename T>
void Process(T value);

extern template void Process<int>(int);
extern template void Process<float>(float);

// cpp.cpp (after)
template<typename T>
void Process(T value) {
    // Implementation
}

template void Process<int>(int);
template void Process<float>(float);

限制:仅适用于模板参数类型已知且数量有限的情况。

步骤 8:验证独立编译

目标:确保优化后的代码能够正确编译。

步骤:

  1. 使用 compile-analysis skill 提取 cpp 文件的编译命令
  2. 验证 cpp 文件能够独立编译
  3. 对于 test_header.cpp 分析,验证其能否使用优化后的头文件编译
  4. 不要运行完整构建——仅验证独立编译

错误处理:

  • 如果编译失败,分析错误
  • 修复缺失的 include 或前向声明
  • 重新验证,直至编译成功
  • 记录所有已应用的修复
步骤 9:衡量优化结果

目标:量化优化的影响。

收集的指标:

  1. 头文件依赖数量:

    • 统计优化前的 include 数量
    • 统计优化后的 include 数量
    • 计算减少百分比
  2. 编译时间:

    • 使用 compile-analysis skill 测量优化前后的编译时间
    • 报告节省的时间
  3. 内存占用:

    • 测量优化前后的头文件大小
    • 报告减少百分比
  4. 代码行数:

    • 头文件 LOC 的减少量
    • cpp 文件中新增的 LOC

结果模板:

## Optimization Results

### Header: frameworks/path/to/header.h

**Before Optimization:**
- Includes: 15
- Header size: 45.2 KB
- Estimated compile impact: High

**After Optimization:**
- Includes: 6
- Header size: 12.8 KB
- Reduction: 60% includes, 72% size

**Changes Made:**
- Moved 12 method implementations to cpp
- Converted 8 includes to forward declarations
- Removed 3 unused includes
- Split constants into separate header
- Applied PIMPL pattern (if applicable)

**Compilation Status:** ✅ Verified standalone compilation
步骤 10:使用 Git 暂存更改

目标:将优化后的文件保留在 Git 中。

步骤:

  1. 使用 git add 暂存已修改的文件:
    • 优化后的头文件
    • 已修改或新建的 cpp 文件
    • 所有新建的拆分头文件
  2. 不要暂存 test_header.cpp(仅供参考)
  3. 向用户展示已暂存的更改

通用优化策略

本节提供根据 ace_engine 中的实际优化案例总结的通用指导。请使用这些策略,为你的情况选择合适的优化方法。

策略选择决策树
┌─────────────────────────────────────────────────────────────┐
│               What type of dependency problem?              │
└─────────────────────────────────────────────────────────────┘
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
   Heavy include        Value type        Cross-namespace
   within same        member from        include with
   namespace          heavy include    only enum usage
        │                   │                   │
        │                   │                   │
        ▼                   ▼                   ▼
┌───────────────┐  ┌───────────────┐  ┌───────────────┐
│  RefPtr<T>    │  │ unique_ptr<T> │  │ Light Include │
│  Forward Decl │  │  + Forward    │  │  Replacement │
│  (Scenario 1) │  │  Declaration  │  │  (Scenario 3) │
└───────────────┘  └───────────────┘  └───────────────┘
场景 1:RefPtr 前向声明(最轻量)

适用情况:

  • 成员已经是 RefPtr<T> 或 WeakPtr<T>
  • 头文件中只需要类型名称
  • 不需要值语义

模式:

// Header
namespace OHOS::Ace::NG {
class ClickRecognizer;  // Forward declaration
}

class ClickEventActuator {
private:
    RefPtr<ClickRecognizer> clickRecognizer_;  // ✅ Works with forward decl
};

// CPP
#include "core/components_ng/gestures/recognizers/click_recognizer.h"

const RefPtr<ClickRecognizer>& ClickEventActuator::GetClickRecognizer() {
    if (!clickRecognizer_) {
        clickRecognizer_ = MakeRefPtr<ClickRecognizer>();  // Full definition needed here
    }
    return clickRecognizer_;
}

⚠️ 重要:RefPtr 成员可以使用前向声明

常见误解:“RefPtr 成员需要完整的类型定义”

✅ 正确理解:RefPtr 成员可以与前向声明完美配合,前提是:

  1. 将所有使用 RefPtr 成员的内联方法移到 .cpp

    • 任何调用 member_->Method() 的方法都必须放在 .cpp 中
    • 任何调用 MakeRefPtr<T>() 的方法都必须放在 .cpp 中
    • 任何参数或返回类型为 RefPtr<T> 的方法都应放在 .cpp 中
  2. 头文件中只保留声明

    • RefPtr<T> member_; ✅ 可与前向声明配合使用
    • RefPtr<T> GetMember(); ✅ 仅保留声明
    • void SetMember(const RefPtr<T>& member); ✅ 仅保留声明
  3. 不要在头文件中内联实例化

    • ❌ 在头文件中调用 MakeRefPtr<T>()
    • ❌ 在头文件中调用 member_->Method()
    • ✅ 两者都必须放在 .cpp 实现中

gesture_recognizer.h 优化示例:

// gesture_recognizer.h - BEFORE (inline implementations)
class NGGestureRecognizer {
    bool IsSystemGesture() const {  // ❌ Inline - needs full GestureInfo definition
        if (!gestureInfo_) return false;
        return gestureInfo_->IsSystemGesture();
    }

    RefPtr<GestureInfo> GetGestureInfo() {  // ❌ Inline - returns RefPtr<GestureInfo>
        return gestureInfo_;
    }

private:
    RefPtr<GestureInfo> gestureInfo_;  // ❌ Requires full definition due to inline methods
};

// gesture_recognizer.h - AFTER (forward declaration + declarations)
namespace OHOS::Ace::NG {
class GestureInfo;  // ✅ Forward declaration only
}

class NGGestureRecognizer {
    bool IsSystemGesture() const;  // ✅ Declaration only

    RefPtr<GestureInfo> GetGestureInfo();  // ✅ Declaration only

private:
    RefPtr<GestureInfo> gestureInfo_;  // ✅ Works with forward declaration!
};

// gesture_recognizer.cpp - Full implementations
#include "core/components_ng/event/gesture_info.h"  // ✅ Full include here

bool NGGestureRecognizer::IsSystemGesture() const {
    if (!gestureInfo_) return false;
    return gestureInfo_->IsSystemGesture();  // ✅ Full definition available
}

RefPtr<GestureInfo> NGGestureRecognizer::GetGestureInfo() {
    return gestureInfo_;  // ✅ Can return RefPtr with full definition
}

核心原则:RefPtr 成员在头文件中不需要完整的类型定义。需要完整类型的是使用该成员的内联方法,而不是成员本身。

优点:

  • ✅ 无需分离析构函数(RefPtr 会管理生命周期)
  • ✅ 代码改动极少
  • ✅ 运行时开销为零
  • ✅ ace_engine 中已有这种模式

实际案例:case-click_event-forward-declaration.md

复杂度:简单


场景 2:将值类型转换为 unique_ptr

适用情况:

  • 来自重量级头文件的值类型成员
  • 成员仅在实现文件(.cpp)中使用
  • 希望移除对重量级头文件的依赖
  • 可以接受少量运行时开销(堆分配)

模式:

// Before
#include "core/components_ng/gestures/gesture_info.h"  // Heavy include

class DragDropRelatedConfigurations {
private:
    DragPreviewOption previewOption_;  // ❌ Value type - needs full definition
};

// After
namespace OHOS::Ace::NG {
struct DragPreviewOption;  // Forward declaration
}

class DragDropRelatedConfigurations {
public:
    ~DragDropRelatedConfigurations() override;  // ✅ Declaration only

    ACE_FORCE_EXPORT const DragPreviewOption& GetOrCreateDragPreviewOption();  // ✅ Return const&

private:
    std::unique_ptr<DragPreviewOption> previewOption_;  // ✅ Smart pointer
};

// CPP
#include "core/components_ng/gestures/gesture_info.h"  // Full include

DragDropRelatedConfigurations::~DragDropRelatedConfigurations() = default;

const DragPreviewOption& DragDropRelatedConfigurations::GetOrCreateDragPreviewOption()
{
    if (!previewOption_) {
        previewOption_ = std::make_unique<DragPreviewOption>();
    }
    if (!previewOption_) {
        static DragPreviewOption defaultInstance;  // ✅ Non-const fallback
        return defaultInstance;
    }
    return *previewOption_;  // ✅ Zero-copy return
}

所需更改:

  1. ✅ 添加前向声明
  2. ✅ 将成员转换为 std::unique_ptr<T>
  3. ✅ 将析构函数移至 cpp(unique_ptr 与前向声明配合使用时必需)
  4. ✅ 将返回值改为 const T&(零拷贝优化)
  5. ✅ 更新 cpp 以解引用智能指针

优点:

  • ✅ 移除对重量级头文件的依赖
  • ✅ 返回值零拷贝优化
  • ✅ 保持 const 正确性
  • ✅ 封装更清晰

代价:

  • ⚠️ 堆分配开销(通常可以忽略)
  • ⚠️ 需要在 cpp 中管理析构函数

实际案例:case-drag-drop-forward-declaration.md

复杂度:中等

场景 3:替换包含(轻量包含策略)

适用场景:

  • 跨命名空间的重型包含
  • 只需要该头文件中的枚举/常量
  • 可以创建新的轻量头文件
  • 大多数类型都可以前向声明

模式:

// Before - Heavy include
#include "core/gestures/drag_event.h"  // ~200 lines, full implementations

class DragEventActuator {
private:
    OptionsAfterApplied optionsAfterApplied_;  // Value type
};

// After - Light include + forward declarations
#include "core/gestures/drag_constants.h"  // ✅ Only enums (~15 lines)

namespace OHOS::Ace {
class DragEvent;  // Forward declaration
}

namespace OHOS::Ace::NG {
struct OptionsAfterApplied;  // Forward declaration
}

class DragEventActuator {
public:
    const OptionsAfterApplied& GetOptionsAfterApplied();  // Declaration only

private:
    std::unique_ptr<OptionsAfterApplied> optionsAfterApplied_;  // ✅ Smart pointer
};

// Create light header: drag_constants.h
namespace OHOS::Ace {
enum class PreDragStatus : int32_t {
    READY = 0,
    PROCESSING = 1,
    FAILED = 2,
};
}

所需更改:

  1. ✅ 创建新的轻量头文件(*_constants.h)
  2. ✅ 将枚举/常量提取到轻量头文件中
  3. ✅ 将重型包含替换为轻量包含
  4. ✅ 为复杂类型添加前向声明
  5. ✅ 将值成员转换为智能指针(如适用)
  6. ✅ 将实现移至 cpp

优点:

  • ✅ 大幅减少依赖(90% 以上)
  • ✅ 命名空间边界更清晰
  • ✅ 代码组织更合理
  • ✅ 轻量头文件可供其他文件复用

代价:

  • ⚠️ 会创建新文件(轻量头文件)
  • ⚠️ 更改比场景 1 更多

实际案例:case-drag-event-include-reduction.md

复杂度:中


策略对比表
方面场景 1:RefPtr场景 2:unique_ptr场景 3:轻量包含
主要适用场景已使用智能指针值类型成员跨命名空间的重型包含
是否创建新文件?否否是(轻量头文件)
析构函数分离不需要必需必需
返回类型更改可能需要 RefPtr<T>&值 → const T&可能需要,也可能不需要
运行时开销零较小(堆分配)较小(堆分配)
依赖减少程度高高非常高(90% 以上)
代码复杂度低中中
最适用于RefPtr/WeakPtr 成员值类型成员跨命名空间依赖

常见实现模式
模式 1:析构函数分离

适用场景:使用前向声明和智能指针成员时

原因:智能指针析构函数需要完整的类型定义

模板:

// Header
class MyClass {
public:
    ~MyClass();  // ✅ Declaration only

private:
    std::unique_ptr<ForwardDeclaredType> member_;
};

// CPP
MyClass::~MyClass() = default;  // ✅ Implementation here

模式 2:零拷贝返回

适用场景:返回由智能指针管理的对象时

原因:避免不必要的对象拷贝

模板:

// ❌ Before - Returns by value (creates copy)
DragPreviewOption GetOption() {
    return *previewOption_;
}

// ✅ After - Returns const reference (zero copy)
const DragPreviewOption& GetOption() {
    if (!previewOption_) {
        static DragPreviewOption defaultInstance;
        return defaultInstance;
    }
    return *previewOption_;
}

模式 3:静态回退实例

适用场景:智能指针可能为空,需要安全的默认值时

模板:

const OptionsAfterApplied& GetOptionsAfterApplied()
{
    if (!optionsAfterApplied_) {
        static OptionsAfterApplied defaultInstance;  // ✅ Non-const
        return defaultInstance;
    }
    return *optionsAfterApplied_;
}

为何非 const:对于带有构造函数的复杂类型,const 静态初始化可能失败。


模式 4:条件解引用

适用场景:将智能指针值传递给期望值类型的函数时

模板:

// When calling: void SetOptionsAfterApplied(const OptionsAfterApplied& options)

// ❌ Wrong - Compile error
frameNode->SetOptionsAfterApplied(optionsAfterApplied_);

// ✅ Correct - Conditional dereference
frameNode->SetOptionsAfterApplied(
    optionsAfterApplied_ ? *optionsAfterApplied_ : OptionsAfterApplied()
);

通用决策清单

使用此清单决定采用哪种优化策略:

步骤 1:分析依赖
  • 确定导致问题的重型包含
  • 搜索该头文件中对该包含所提供类型的所有用法
  • 对用法进行分类:
    • 成员变量(值类型或智能指针)
    • 函数参数/返回值
    • 基类继承
    • 模板实例化
步骤 2:选择策略

如果已使用 RefPtr/WeakPtr → 场景 1

  • ✅ 只需添加前向声明
  • ✅ 无需其他更改

如果是同一命名空间中的值类型成员 → 场景 2

  • ✅ 转换为 unique_ptr
  • ✅ 将析构函数移至 cpp
  • ✅ 返回 const&,而非按值返回

如果是跨命名空间包含且只使用枚举 → 场景 3

  • ✅ 创建轻量头文件(*_constants.h)
  • ✅ 将重型包含替换为轻量包含
  • ✅ 为复杂类型添加前向声明
  • ✅ 将值成员转换为智能指针(如适用)

如果以上都不适用 → 考虑 PIMPL

  • 详细指南请参见 references/pimp-guide.md
步骤 3:实施更改
  • 添加前向声明
  • 将成员转换为智能指针(如适用)
  • 将析构函数移至 cpp(如果使用 unique_ptr)
  • 将内联实现移至 cpp
  • 将返回类型更新为 const&
  • 更新 cpp,以解引用智能指针
  • 在 cpp 中添加完整包含
  • 验证独立编译
Step 4:验证结果
  • 检查头文件能否独立编译
  • 验证 cpp 文件在包含完整头文件后能否编译
  • 确认依赖文件中没有编译错误
  • 衡量依赖项的减少情况

常见陷阱
陷阱 1:忘记分离析构函数

症状:

error: invalid application of 'sizeof' to an incomplete type

原因:使用前向声明时,unique_ptr 需要在 cpp 文件中定义析构函数

解决方案:将析构函数的实现移至 cpp 文件


陷阱 2:转换为 unique_ptr 后仍按值返回

症状:不必要的对象复制,可能导致性能下降

原因:将成员改为 unique_ptr,却仍按值返回

解决方案:将返回类型改为 const T&


陷阱 3:忘记解引用

症状:将智能指针传给函数时出现编译错误

原因:需要值类型时,忘记解引用 unique_ptr

解决方案:使用条件解引用模式:

function(ptr ? *ptr : Type());

陷阱 4:const 静态初始化失败

症状:复杂类型的静态初始化失败

原因:对复杂构造函数使用 static const T

解决方案:使用 static T(非 const)作为后备方案


其他资源

参考文件

如需了解详细技巧和示例,请参阅:

  • references/patterns.md - 头文件优化的常见重构模式
  • references/pimp-guide.md - PIMPL 模式详细指南,包含 ace_engine 示例
  • references/forward-declaration.md - 前向声明最佳实践
  • references/case-split-enums.md - 真实案例:将枚举从 drag_event.h 拆分至 drag_constants.h(依赖项减少 90% 以上)
  • references/case-click_event-forward-declaration.md - 真实案例:click_event.h 的 RefPtr 前向声明优化(体积减少 35%,包含智能指针优化模式)
  • references/case-drag-drop-forward-declaration.md - ✨ 新增:真实案例:将 drag_drop_related_configuration.h 中的值类型成员转换为 unique_ptr
  • references/case-drag-event-include-reduction.md - ✨ 新增:真实案例:drag_event.h 中跨命名空间依赖的轻量级 include 替换策略
示例

examples/ 中的可运行示例:

  • before-after/ - 并排展示优化前后的对比
  • pimpl-example/ - 完整的 PIMPL 实现示例
  • split-header/ - 头文件拆分示例
脚本

scripts/ 中的实用脚本:

  • analyze-includes.sh - 分析头文件的 include 依赖
  • extract-includes.py - 从头文件中提取 include 统计信息

常见陷阱

请勿:

  • 以任何理由修改 test_header.cpp
  • 在优化期间运行完整构建
  • 修改业务逻辑或更改行为
  • 创建不必要的额外文件(拆分场景除外)
  • 将优化范围扩展到规定步骤之外
  • 忘记验证能否独立编译

务必:

  • 通过独立编译验证每项优化
  • 记录更改及其理由
  • 使用 compile-analysis skill 提取编译命令
  • 专注于减少头文件依赖
  • 保持功能等效
  • 优化成功后使用 git add 暂存更改

仅分析模式

当用户指定“只分析不进行修改”时:

  1. 执行所有分析步骤(1-9)
  2. 生成详细报告,包含:
    • 当前头文件依赖分析
    • 建议的优化及理由
    • 预期改进
    • 拟议的具体代码更改
  3. 向用户提交报告
  4. 在进行修改前征求批准
  5. 在用户批准前,不得修改任何文件

与 compile-analysis Skill 集成

此 Skill 与 compile-analysis skill 集成,用于衡量编译效率:

  1. 使用 compile-analysis 提取编译命令
  2. 优化前测量编译时间
  3. 应用优化
  4. 优化后测量编译时间
  5. 比较并报告结果

需要提取编译命令时,请调用:

Use compile-analysis skill to extract compilation command for [file.cpp]

评论

支持 Markdown