65.9K
CodeProject 正在变化。 阅读更多。
Home

C++ 对象析构事件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (3投票s)

2012年8月8日

CPOL

3分钟阅读

viewsIcon

23623

downloadIcon

254

虚函数表修改和析构事件处理。

引言

本教程尝试描述如何通过修改对象的虚函数表(vtable)条目来订阅函数调用事件,并特别演示通过修改 vtable 来挂钩对象析构函数的代码示例。 通常,这在 C++ 编码中可能不是必需的,但对于读者来说,这是关于 vtable 和虚函数的额外信息。

背景 

需求产生于我们拥有超过 100,000 个对象(例如 Book)存储在一个容器(例如 Library)中,并且 Library 一直被工作线程修改。 容器中的 Books 又在 GUI 控件中以活动 Book 的形式可视化。 问题出现在活动 Book 被工作线程删除,而 GUI 线程尝试访问相同对象时。
评估了几种解决方案,v-table 重载是其中之一。互联网上有很多关于构建 v-table 的讨论,但这些看起来可能不够通用,因为编译器可以自由选择 v-table 的格式。 因此,决定使用编译器本身来构建我们的自定义 v-table。
这种方法中唯一的假设是 v-table 指针存储在对象内存布局的前 4 个字节中,如果这个假设不正确,那么示例代码将无法工作。

自定义 v-table 

实现目标更复杂的部分是构建 v-table,但 v-table 的内存布局可能与编译器相关。 因此,我们考虑使用编译器智能来构建我们的自定义 v-table,方法是从 Book 类继承 book_New 类(没有任何状态信息,如成员变量或访问基类成员变量),并在必要时重载虚函数。 现在,book_New 的 v-table 将被挂钩到 book 对象的实例上。

使用代码

示例代码被实现为一个模板,用于挂钩对象析构(这是我们的主要目的)。 示例代码将仅支持挂钩一个对象和一个观察者,但该模板应被修改为支持多个对象并挂钩其他成员函数。

让我们看一下 Book 类

class Book {
 
protected: 
   char* name_;
public:
   Book(char* pstr) {
 
      name_=_strdup(pstr);
   };
   Book(){};
   void Display() {printf("Book Name:%s\n",name_);};
 
   virtual ~Book() {
 
      printf("virtual BOOK::~BOOK for :%s\n",name_);
      delete name_ ;
   };
}; 

这是一个简单的 C++ 类,用于记住书名并具有虚析构函数,保留虚析构函数的原因是我们需要挂钩析构事件。

析构函数 Hook 

接下来要看的一段代码是 DestructorHook 模板类

IDestructorObserver 接口

class IDestructorObserver {
 
public:
  // invoked when hooked object is destroyed
  virtual void OnDestroy(T* pObj) = 0;
};

这是一个简单的 C++ 接口,它有一个用于析构的事件回调,并将有问题的对象作为参数传递。 容器应该实现这个接口,以便获得感兴趣对象的析构通知。

现在让我们检查模板类的成员变量

T*    data_; // Generic pointer to be stored
IDestructorObserver* lifeTimeObserver_;
void* vtable_;

data_ 指的是感兴趣的对象,这里这个变量最终存储 Book 对象,lifeTimeObserver_ 用于将事件分发给感兴趣的各方,最后 vtable_ 用于存储 Book 的 vtable 指针,以便在取消挂钩时恢复。 这些数据成员应保存在 vector 或 map 类型的数据结构中,以支持多个对象和多个观察者。

实例方法将简单地初始化 lifeTimeObserver_ 成员并返回 Hook 对象。 返回的 hook 对象应用于挂钩 T 类型的对象 

static DestructorHook<T>& Instance(IDestructorObserver* proc) {
  staticObj.lifeTimeObserver_ =proc;
  staticObj.name_=_strdup("Librarian Hook");
  return staticObj;
};

Hook 方法将备份被挂钩对象的 vtable 并将其保留以供将来参考,然后用我们的自定义 vtable 覆盖,如下所示 

void Hook(T* objPtr) {
 
  UnHook();
  // copy first four bytes in MSVC this points to __vfptr
  memcpy(&vtable_,objPtr,sizeof(void*));
  // __vfptr of objPtr with ours
  memcpy(objPtr,this,sizeof(void*));
  data_ = objPtr;
}

析构函数,当被挂钩对象被删除时,将调用此函数,在过滤掉自身析构后,事件将被分派给订阅者

virtual ~DestructorHook() {
 
  // object is getting destroyed
  // so we have to unhook anyway
  staticObj.unHook();
  if(this == staticObj.data_) {
 
     // destructor invoked in the context of hooked object
     // so dispatch event
     staticObj.lifeTimeObserver_->OnDestroy(staticObj.data_);
     // no need to call the destructor as the destruction cycle 
     // continues to call base destructor
     staticObj.data_ = 0;
  }
}

最后是主函数,它初始化 Books 对象,Librarian 对象将订阅析构事件。 稍后,Book 对象被销毁,librarian 将收到被挂钩对象的通知 

int _tmain(int argc, _TCHAR* argv[]) {
 
   // initialize chip
   Book * pBook[5];
   char name[20] = {0};
   for(int i = 0 ; i < 5; i++) {
 
      sprintf_s(name,"book%d",(i+1));
      pBook[i] = new Book(name);
   }
 
   Librarian librarian;
   BookDestructorHook& hook_ = BookDestructorHook::Instance(static_cast<BookDestructorHook::IDestructorObserver*>(&librarian));
   hook_.Hook(pBook[3]);
 
 
   printf("Display available books\n");
   for(int i = 0 ; i < 5; i++) {
 
      pBook[i]->Display();
   }
 
   printf("\n\nDeleting all Book Librarian should get notification for\t");
   pBook[3]->Display();
 
   for(int i = 0 ; i < 5; i++) {
 
      delete pBook[i];
   }
 
   return 0;
}

 

输出: 

结论 

本文展示了一个非常简单的示例,说明如何修改 C++ vtable 条目来处理对象析构事件。 虽然我们没有使用这种方法,因为我们更喜欢引用计数机制,但个人认为这种替代方法应在时间紧迫的应用程序中考虑,在这种应用程序中,会持续创建和销毁数万本 Book,但容器只对单个对象感兴趣。 通过采用这种方法,剩余的对象不应承担引用计数实现的负担。



© . All rights reserved.