前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Nebula3学习笔记(3): 核心库

Nebula3学习笔记(3): 核心库

作者头像
逍遥剑客
发布2018-05-23 11:11:26
5810
发布2018-05-23 11:11:26
举报

核心子系统

核心库(Core namespace)实现了这些特性:

  • 一个实现了引用计数的RefCounted基类
  • 一个运行时类型信息系统(RTTI)
  • 一个模板智能指针, 用于处理RefCounted对象的生命周期
  • 一个由类名创建C++对象实例的工厂机制
  • 一个中央Server对象用于建立基本的Nebula3运行环境

对象模型

Nebula3在C++对象模型的基础之上实现了下面这些新特性:

  • 基于引用计数和智能指针的生命周期管理
  • 基于类名或四字符编码的对象创建
  • 一个运行时类型信息系统

实现一个新的Nebula3类

当实现一个新的类时首先要考虑它是一个传统的C++类还是要从Core::RefCounted继承. 以下几点可以帮你找到答案:

  • 如果这个类需要使用Nebula3的扩展对象特性, 如引用计数, RTTI等, 则它必须从Core::RefCounted继承.
  • 如果这个类是一个典型的小工具类, 如动态数组, 数学向量, 或其它相似的东西, 那么它从Core::RefCounted 继承也没有什么意义.

从Core::RefCounted类继承有一些限制:

  • RefCounted派生类不应该在栈上创建对象, 因为栈对象的生命周期是由C++来管理的(他们会在离开当前上下文时被销毁, 从而绕过了Nebula3的引用计数生命周期 管理)
  • RefCounted的派生类只有一个默认的构造函数.
  • RefCounted的派生类必须有一个虚析构函数.
  • RefCounted的派生类不能进行拷贝, 因为这样会造成引用计数机制混乱.

要使用Nebula3的对象模型特性, 除了需要从Core::RefCounted继承外, 还需要在头文件新类的声明中进行额外的标注:

一个标准的RefCounted派生类一般这样声明:

代码语言:javascript
复制
namespace MyNamespace
{
class MyClass : public Core::RefCounted
{
DeclareClass(MyClass);
public:
  /// constructor、
  MyClass();
  /// destructor
  virtual ~MyClass();
  ...
 };
 RegisterClass(MyClass);

注意DeclareClass()宏, 构造函数, 析构函数还有类外面的RegisterClass()宏. DeclareClass()宏加入了RTTI和工厂机制所需的最小代价的信息, 它隐藏了Nebula3的对象模型, 希望可以在不影响已有类的基础进上进行内部机制的变更. RegisterClass()宏是可选的, 它把当前类在中央工厂进行注册. 如果你知道这个类永远不会由类名或四字符编码进行创建, 这个宏可以省略.

在这个类的.cpp文件里需要包含Nebula3特有的信息:

代码语言:javascript
复制
namespace MyNamespace
 {
 ImplementClass(MyNamespace::MyClass, 'MYCL', Core::RefCounted);
 }

ImplementClass()宏注册类的RTTI机制, 第一个参数描述了类的名字(注意命名空间必须包含). 第二个参数是类的四字符编码, 它必须是所有类中唯一的(如果有重复, 你会在启动程序时得到一个错误提示). 第三个参数是父类的名字, 用于RTTI系统去构造类的关系树.

引用计数和智能指针

Nebula3使用传统的引用计数来管理对象的生命周期. 一个模板智能指针类Ptr<>对程序员隐藏了引用计数的实现细节. 一般来说, 应该一直使用智能指针指向RefCounted的派生对象, 除非你能肯定在给出的代码块中这个对象的引用计数不会发生变化.

智能指针相对于一般指针有很多好处:

  • 访问一个空指针会给你一个断言警告而不是一个内存错误
  • 你不需要对引用计数的对象调用AddRef()或Release() (事实上如果你调了, 会了发生严重的错误)
  • 智能指针可以在容器类里良好地工作, 一个智能指针的数组会消除所有的一般指针需要的生命周期管理, 你永远不需要考虑去释放指针所指针的对象, 数组包含的像是真正的C++对象一样
  • 用智能指针不需要考虑指针的所属, 不需要为谁delete对象而烦恼

智能指针也有一些缺点:

  • 性能: 拷贝和赋值会引起对象的引用计数的变化, 解除引用会引起指针的断言检查. 这导致的性能消耗一般是可以忽略的, 但是你最好保证它不在内部循环中发生.
  • 应该销毁的对象还存在: 因为智能指针管理的对象只有在最后一个引用放弃时才会销毁, 这样会使对象存在超过预订的时间. 这经常会导致一个BUG的产生. 不过引用计数泄露(程序退出时还仍然存在的对象)时Nebula3会提醒你.

创建Nebula3对象

从Core::RefCounted继承的类可以通过3种不同的方式进行创建:

直接通过静态的Create方法:

代码语言:javascript
复制
Ptr<MyClass>myObj=MyClass::Create();

静态的Create()方法是之前提到的DeclareClass()宏加入的, 相对于new操作符来说, 它并没有多做什么. 注意正确使用智能指针来保存新建的对象.

另一种创建方式是通过类名:

代码语言:javascript
复制
using namespace Core;
Ptr<MyClass> myObj = (MyClass*)Factory::Instance()->Create("MyNamespace::MyClass");

当你在运行时通过类名来创建十分有用, 特别是对象的反序列化和脚本接口的使用. 注意类型转换是必须的, 因为工厂的Creat()方法返回的是RefCounted指针.

由类名创建的变种是根据四字符编码进行创建:

代码语言:javascript
复制
using namespace Core;
using namespace Util;
Ptr<MyClass> myObj = (MyClass*) Factory::Instance()->Create(FourCC('MYCL'));

这个方法看上去没有那个直观, 但是它比类名创建快得多. 并且四字符编码比类名占用的空间更少, 这更利于对象写入二进制流或从中读取.

运行时类型信息系统

Nebula3的RTTI系统可以让你在运行时访问对象的类型, 检查一个对象是不是某个类的实例, 或者某个派生类的实例. 你也可以直接获得一个对象的类名和四字符编码. 所有这些功能是由DeclareClass() 和 ImplementClass() 宏在背后实现的.

这时有示例程序:

代码语言:javascript
复制
using namespace Util;
    using namespace Core;

  // check whether an object is instance of a specific class
  if (myObj->IsInstanceOf(MyClass::RTTI))
  {
  // it's a MyClass object
  }
  
  // check whether an object is instance of a derived class
  if (myObj->IsA(RefCounted::RTTI))
  {
  // it's a RefCounted instance or some RefCounted-derived instance
  }

 // get the class name of my object, this yields "MyNamespace::MyClass"
  const String& className = myObj->GetClassName();

  // get the fourcc class identifier of my object, this yields 'MYCL'
  const FourCC& fourcc = myObj->GetClassFourCC();

你也可以向中央工厂查询一个类是否已经注册:

代码语言:javascript
复制
 1:     using namespace Core;
 2:
 3:  // check if a class has been registered by class name
 4:  if (Factory::Instance()->ClassExists("MyNamespace::MyClass"))
 5:  {
 6:  // yep, the class exists
 7:  }
 8:  
 9:  // check if a class has been registered by class fourcc code
10:  if (Factory::Instance()->ClassExists(FourCC('MYCL')))
11:  {
12:  // yep, the class exists
13:  }

Nebula3单件

很多Nebula3的核心对象都是单件, 就是只存在一个实例, 并且所有其它对象都知道它.

你可以通过静态方法Instance()来访问单件, 它返回唯一实例的一个指针. 返回的指针保证是合法的. 如果在调用Instance()方法时对象实例不存在, 一个断点会被抛出:

1:     // obtain a pointer to the Core::Server singleton 2:     Ptr<Core::Server> coreServer = Core::Server::Instance();

你也可以检查单件是否存在:

1:     // does the Core::Server object exist? 2:     if (Core::Server::HasInstance()) 3:     { 4:         // yep, the core server exists 5:     }

Nebula3提供了一些辅助的宏来实现单件:

代码语言:javascript
复制
 1: // declare a singleton class
 2: class MySingletonClass : public Core::RefCounted
 3: {
 4:  DeclareClass(MySingletonClass);
 5:  DeclareSingleton(MySingletonClass);
 6: public:
 7:  /// constructor
 8:     MySingletonClass();
 9:  /// destructor
10:  virtual ~MySingletonClass();
11:  ...
12: };
13:
14: // implement the singleton class
15: ImplementClass(MyNamespace::MySingletonClass, 'MYSC', Core::RefCounted);
16: ImplementSingleton(MyNamespace::MySingletonClass);
17:
18: //------------------------------------------------------------------------------
19: /**
20:     Implements the Singleton constructor.
21: */
22: MySingletonClass::MySingletonClass()
23: {
24:  ConstructSingleton;
25: }
26:
27: //------------------------------------------------------------------------------
28: /**
29:     Implements the Singleton destructor.
30: */
31: MySingletonClass:~MySingletonClass()
32: {
33:  DestructSingleton;
34: }

DeclareSingleton()和ImplementSingleton()宏跟DeclareClass()和ImplementClass()宏差不多.它们在类中添加了一些静态方法(也就是Instance()和HasInstance()). 类的构造函数和析构函数必须包含ConstructSingletonDestructSingleton宏. ContructSingleton初始化了一个私有的单件指针并保证没有其它的类实例存在(如果不是, 会抛出断言). DestructSingleton让私有的单件指针无效化.

单件的访问默认是只有本地线程. 这意味着在一个线程中创建的单件无法被其他线程访问. 这使得”并行Nebula”大大简化了多线程编程. “并行Nebula”的基本思想是, 一个典型的Nebula3应用程序包含一些”Fat线程”, 每一个Fat线程都是运行在一个单独的CPU核心上. Fat线程可以用于实现异步IO, 渲染, 物理等等. 每一个Fat线程都初始化了它们自己的Nebula3运行环境, 它们执行特性任务所需的最少依赖. 这基本上消除了大部分Nebula3代码的同步问题, 并且把线程相关的代码集中到一个明确定义的代码区域中. “并行Nebula”的另一个好处就是, 程序员在多线程环境中编程时不需要关心太多. 大多数Nebula3代码看起来就像单线程代码一样, 但是它们却运行在各自的Fat线程中.

性能与内存占用的考虑

Nebula3核心层的一个设计目标就是减少底层代码的内存占用, 来更好的适应微型平台, 像手持设备. 这里有一些已经完成的目标:

  • RefCounted 类在每个实例中只增加了4byte用于引用计数.
  • RTTI机制在开头增加了30 到 60 byte, 但是这是对于每个类来说的, 而是不是每个实例.
  • 一个智能指针仅仅4 byte, 就像普通指针一样.
  • 一些监控结构只会在debug模型下创建, 特别是用来检测引擎计数泄露的RefCountedList.

这里一些用三种不种的创建方法创建一百万个RefCounted 对象所需的时间信息. 这些时间信息是在台Intel Pentium 800 MHz的笔记本上得出的.  

  • Create(): 0.29 seconds
  • FourCC: 0.65 seconds
  • 类名: 1.45 seconds
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2008年07月03日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档