关于 header-optimization
header-optimization 是针对 ace_engine 的 C++ 头文件优化 skill,供维护代码和分析编译开销的开发者使用。它通过移出较长实现、精简依赖和使用前置声明来降低头文件耦合,并包含仅分析模式。
主要功能
- 01将较长的内联实现移至 CPP
- 02移除未使用的头文件依赖
- 03以适用的前置声明替代包含
- 04按需拆分常量和枚举头文件
- 05支持只分析并给出修改建议
适用场景
- 01降低特定头文件的编译开销
- 02检查 test_header.cpp 的依赖分析结果
- 03在不修改文件的情况下制定优化方案
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ openharmonyinsight/ openharmony-skills --skill header-optimization安装后在 Claude Code 中输入/header-optimization确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdexamples/README.mdexamples/before-after/forward_declaration_example.mdinline_extraction_example.mdreferences/case-click_event-forward-declaration.mdcase-drag-drop-forward-declaration.mdcase-drag-event-include-reduction.mdcase-split-enums.mdforward-declaration.mdpatterns.mdpimp-guide.mdscripts/analyze-includes.shextract-includes.py详细介绍
ACE Engine 的头文件优化
通过系统性地减少依赖和重构实现,提升 ace_engine 中 C++ 头文件的编译效率。本技能侧重于尽量减少头文件依赖并将实现移出头文件,从而缩短编译时间、降低内存占用。
何时使用此技能
在以下情况调用此技能:
- 优化特定头文件的编译效率
- 使用 test_header.cpp 分析头文件依赖
- 减少 ace_engine 中的头文件依赖
- 分析特定头文件的编译性能
- 用户指定“只分析不进行修改”
关键规则
优化时必须遵守以下限制:
- 保留业务逻辑——只修改结构,不改动功能
- 尽量减少新建文件——直接编辑当前头文件/cpp 文件;仅在拆分头文件时新建文件
- 限制范围——只执行关键步骤中定义的工作,不扩大修改范围
- 独立编译——使用 compile-analysis 技能验证单个文件的编译情况,绝不进行完整构建
- test_header.cpp 只读——绝不修改 test_header.cpp;它仅用于统计依赖
优化工作流
步骤 1:分析输入和模式
确定优化目标和模式:
- 如果输入是头文件(
.h):直接优化该头文件 - 如果输入是
test_header.cpp:从其内容中提取头文件并优化该头文件 - 检查用户是否指定了“只分析不进行修改”(仅分析模式)
在仅分析模式下:
- 执行所有分析步骤
- 生成优化建议
- 不要修改任何文件
- 呈现分析结果和建议的修改,供用户审批
步骤 2:将内联实现移至 CPP 文件
目标:将所有超过 3 行的方法实现从头文件移至 cpp 文件。
流程:
- 读取目标头文件
- 找出所有实现超过 3 行的方法(包括静态方法)
- 如果对应的 cpp 文件不存在,则创建该文件
- 将方法实现移至 cpp 文件
- 头文件中仅保留声明
- 模板方法请参阅步骤 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:移除不必要的头文件包含
目标:消除未使用的头文件依赖。
流程:
- 列出头文件中的所有
#include指令 - 对每个被包含的头文件,确认是否实际使用:
- 在文件中搜索来自该头文件的类型
- 检查成员变量、函数参数或返回类型是否使用这些类型
- 验证基类继承关系
- 移除没有实际用途的包含
- 在分析报告中说明移除原因
示例:
// 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:将包含转换为前向声明
目标:在可行的情况下,用前向声明替换完整的头文件包含。
流程:
- 分析每个剩余的头文件包含实际需要提供的内容:
- 仅需要类型名称(指针、引用、函数参数)
- 需要类型定义(必须提供完整的类定义)
- 静态常量或枚举
- 仅需要类型名称时,改用前向声明:
// Before #include "core/components_ng/base/frame_node.h" // After namespace OHOS::Ace::NG { class FrameNode; } // namespace OHOS::Ace::NG - 仅在以下情况保留完整包含:
- 继承该类
- 类包含该类型的成员变量
- 模板实例化需要完整定义
- 访问静态常量或内联方法
前向声明模板:
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>
}
分析流程:
- 搜索头文件中的类型用法(例如
GestureEventFunc、GestureJudgeFunc) - 使用 grep/搜索工具查找这些类型的定义位置
- 直接包含定义该类型的头文件
- 对 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 的间接依赖
应用的关键技巧:
- 将
#include "click_recognizer.h"替换为前向声明 - 为类型定义添加精确的 include(
GestureEventFunc、GestureJudgeFunc) - 为
ClickInfo添加跨命名空间的前向声明 - 将 12 个内联实现移至 cpp 文件
步骤 5:拆分常量和枚举的头文件
目标:将常用常量或枚举提取到单独的头文件中,以降低耦合度。
适用情况:
- 原始头文件仅因少量常量或枚举值而被包含
- 多个文件依赖这些常量,但不依赖完整头文件
- 拆分能带来可衡量的编译收益
步骤:
- 找出其他文件所依赖的常量或枚举
- 创建新的头文件(例如,
original_types.h、original_constants.h) - 将提取的定义移至新头文件
- 更新原始头文件以包含拆分后的头文件
- 更新依赖文件,使其仅包含较小的头文件
示例:
// 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 稳定,但实现经常变化
- 减少头文件依赖能显著缩短编译时间
步骤:
- 创建一个 Impl 类(在头文件中前向声明)
- 将私有成员和繁重的依赖移至 cpp 文件中的 Impl 类
- 将私有成员替换为 std::unique_ptr
- 更新构造函数和析构函数,以管理 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:优化模板方法
目标:通过显式实例化降低模板编译开销。
步骤:
- 找出头文件中的模板方法
- 检查是否可以将模板转换为带显式实例化的普通方法
- 将模板实现移至 cpp 文件
- 为常用类型添加显式实例化声明
示例:
// 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:验证独立编译
目标:确保优化后的代码能够正确编译。
步骤:
- 使用 compile-analysis skill 提取 cpp 文件的编译命令
- 验证 cpp 文件能够独立编译
- 对于 test_header.cpp 分析,验证其能否使用优化后的头文件编译
- 不要运行完整构建——仅验证独立编译
错误处理:
- 如果编译失败,分析错误
- 修复缺失的 include 或前向声明
- 重新验证,直至编译成功
- 记录所有已应用的修复
步骤 9:衡量优化结果
目标:量化优化的影响。
收集的指标:
-
头文件依赖数量:
- 统计优化前的 include 数量
- 统计优化后的 include 数量
- 计算减少百分比
-
编译时间:
- 使用 compile-analysis skill 测量优化前后的编译时间
- 报告节省的时间
-
内存占用:
- 测量优化前后的头文件大小
- 报告减少百分比
-
代码行数:
- 头文件 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 中。
步骤:
- 使用
git add暂存已修改的文件:- 优化后的头文件
- 已修改或新建的 cpp 文件
- 所有新建的拆分头文件
- 不要暂存 test_header.cpp(仅供参考)
- 向用户展示已暂存的更改
通用优化策略
本节提供根据 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 成员可以与前向声明完美配合,前提是:
-
将所有使用 RefPtr 成员的内联方法移到 .cpp
- 任何调用
member_->Method()的方法都必须放在 .cpp 中 - 任何调用
MakeRefPtr<T>()的方法都必须放在 .cpp 中 - 任何参数或返回类型为
RefPtr<T>的方法都应放在 .cpp 中
- 任何调用
-
头文件中只保留声明
RefPtr<T> member_;✅ 可与前向声明配合使用RefPtr<T> GetMember();✅ 仅保留声明void SetMember(const RefPtr<T>& member);✅ 仅保留声明
-
不要在头文件中内联实例化
- ❌ 在头文件中调用
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
}
所需更改:
- ✅ 添加前向声明
- ✅ 将成员转换为
std::unique_ptr<T> - ✅ 将析构函数移至 cpp(unique_ptr 与前向声明配合使用时必需)
- ✅ 将返回值改为
const T&(零拷贝优化) - ✅ 更新 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,
};
}
所需更改:
- ✅ 创建新的轻量头文件(*_constants.h)
- ✅ 将枚举/常量提取到轻量头文件中
- ✅ 将重型包含替换为轻量包含
- ✅ 为复杂类型添加前向声明
- ✅ 将值成员转换为智能指针(如适用)
- ✅ 将实现移至 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_ptrreferences/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-9)
- 生成详细报告,包含:
- 当前头文件依赖分析
- 建议的优化及理由
- 预期改进
- 拟议的具体代码更改
- 向用户提交报告
- 在进行修改前征求批准
- 在用户批准前,不得修改任何文件
与 compile-analysis Skill 集成
此 Skill 与 compile-analysis skill 集成,用于衡量编译效率:
- 使用 compile-analysis 提取编译命令
- 优化前测量编译时间
- 应用优化
- 优化后测量编译时间
- 比较并报告结果
需要提取编译命令时,请调用:
Use compile-analysis skill to extract compilation command for [file.cpp]