在MFC框架下的AutoCAD二次开发项目中,我们经常会遇到一个经典的设计模式问题:为什么某些类的初始化和清理操作不是对称出现的?具体到ArxDbgUiTdmReactors这个对话框类,我们观察到构造函数中显式初始化了三个子对话框成员变量,但却没有对应的析构函数或清理方法,反而在另一个独立的cleanUpReactors()方法中处理资源释放。
这种现象在大型框架开发中并不罕见。以AutoCAD ObjectARX为例,其内部管理着多种类型的反应器(Reactor):
这些反应器的生命周期管理往往采用"按需创建,统一清理"的策略。在构造函数中初始化对话框控件是MFC的标准做法,因为这些UI元素在对话框生命周期内必定存在。而反应器资源则不同,它们可能:
cpp复制ArxDbgUiTdmReactors::ArxDbgUiTdmReactors(CWnd* parent)
: CAcUiTabMainDialog(ArxDbgUiTdmReactors::IDD, parent, ArxDbgApp::getApp()->dllInstance())
{
SetDialogName(_T("ArxDbg-Reactors"));
// UI组件初始化
m_tdcSysReactors = new ArxDbgUiTdcSysReactors;
m_tdcTransientReactors = new ArxDbgUiTdcTransientReactors;
m_tdcPersistentReactors = new ArxDbgUiTdcPersistentReactors;
// MFC标准数据交换初始化
//{AFX_DATA_INIT(ArxDbgUiTdmReactors)
//}}AFX_DATA_INIT
}
这里的关键点在于:
cpp复制void ArxDbgUiTdmReactors::cleanUpReactors()
{
// 实际清理的是子对话框类中的静态/全局资源
ArxDbgUiTdcSysReactors::cleanUpReactors();
ArxDbgUiTdcTransientReactors::cleanUpReactors();
ArxDbgUiTdcPersistentReactors::cleanUpReactors();
}
这个设计体现了几个重要考量:
这种不对称设计常见于以下场景:
在AutoCAD开发中,反应器注册就是典型案例:
cpp复制// 伪代码示例
void registerReactorIfNeeded()
{
static bool isRegistered = false;
if(!isRegistered && someCondition){
acdbHostApplicationServices()->workingDatabase()
->addReactor(pReactor);
isRegistered = true;
}
}
在MFC/AutoCAD混合开发中,应遵循以下资源管理原则:
| 资源类型 | 初始化时机 | 清理时机 | 管理方式 |
|---|---|---|---|
| UI控件 | 构造函数 | 析构函数 | 对象生命周期绑定 |
| 全局反应器 | 首次使用时 | 应用退出时 | 引用计数/标志位 |
| 临时对象 | 命令执行时 | 命令结束时 | 栈对象/智能指针 |
| 持久数据 | 文档打开时 | 文档关闭时 | 文档事务管理 |
cpp复制void faultyCleanup()
{
AcDbObject* pObj = new AcDbObject;
pObj->close(); // 忘记delete pObj!
}
cpp复制static MyReactor reactor; // 可能在其他静态对象析构后仍被调用
推荐采用RAII包装器:
cpp复制class ScopedReactor {
AcDbObjectReactor* m_reactor;
public:
ScopedReactor(AcDbObject* pObj) {
m_reactor = new MyReactor;
pObj->addReactor(m_reactor);
}
~ScopedReactor() {
if(m_reactor) {
pObj->removeReactor(m_reactor);
delete m_reactor;
}
}
};
在ObjectARX开发中,要特别注意:
反应器通知顺序:
数据库锁定策略:
cpp复制// 正确做法
acdbTransactionManager->startTransaction();
try {
// 操作数据库
acdbTransactionManager->endTransaction();
} catch (...) {
acdbTransactionManager->abortTransaction();
}
cpp复制void handleDocumentActivation(AcApDocument* pDoc)
{
AcDbDatabase* pDb = pDoc->database();
// 更新反应器关联的数据库
}
当遇到资源泄漏问题时:
cpp复制#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
lisp复制(arxwho) ; 列出加载的ARX模块
(reactors) ; 显示当前注册的反应器
cpp复制_CrtMemState s1, s2, s3;
_CrtMemCheckpoint(&s1);
// 执行操作
_CrtMemCheckpoint(&s2);
if(_CrtMemDifference(&s3, &s1, &s2)) {
_CrtMemDumpStatistics(&s3);
}
对于高频使用的反应器:
cpp复制virtual void modified(const AcDbObject* pObj) override {
if(!isPerformanceCritical) {
CAcModuleResourceOverride resOverride;
// 处理修改
}
}
cpp复制void bulkOperation()
{
acDocManager->lockDocument(acDocManager->curDocument());
// 批量修改
acDocManager->unlockDocument(acDocManager->curDocument());
}
cpp复制pReactor->setPriority(AcDb::kReactorPriorityHigh); // 关键反应器设高优先级
在MFC与AutoCAD混合开发中,理解这种不对称设计背后的考量,可以帮助我们写出更健壮的代码。记住三个关键原则:
实际项目中,我建议为每种反应器类型创建专门的管理类,统一处理初始化和清理逻辑。例如:
cpp复制class ReactorManager {
static std::vector<AcDbObjectReactor*> m_reactors;
public:
static void addReactor(AcDbObjectReactor* p) {
m_reactors.push_back(p);
}
static void cleanup() {
for(auto p : m_reactors) {
if(p) delete p;
}
m_reactors.clear();
}
};
这样既保持了代码的整洁性,又确保了资源的安全释放。