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

核心子系统

核心库(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派生类一般这样声明:

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特有的信息:

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方法:

Ptr<MyClass>myObj=MyClass::Create();

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

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

using namespace Core;
Ptr<MyClass> myObj = (MyClass*)Factory::Instance()->Create("MyNamespace::MyClass");

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

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

using namespace Core;
using namespace Util;
Ptr<MyClass> myObj = (MyClass*) Factory::Instance()->Create(FourCC('MYCL'));

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

运行时类型信息系统

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

这时有示例程序:

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();

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

 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提供了一些辅助的宏来实现单件:

 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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java学习网

Java面试题系列之基础部分(六)——每天学5个问题

Java基础部分学习的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语法,集合的语法,io的语法,虚拟机方面的语法,这些都是最基...

2665
来自专栏余林丰

Effective Java通俗理解(下)

第31条:用实例域代替序数   枚举类型有一个ordinal方法,它范围该常量的序数从0开始,不建议使用这个方法,因为这不能很好地对枚举进行维护,正确应该是利用...

2689
来自专栏java一日一条

10个惊艳的Swift单行代码

几年前,一篇表述“10个Scala函数式单行代码”的文章非常受欢迎,并且随后立马出现了其他的语言版本,例如Haskell版本,Ruby版本,Groovy版本,C...

962
来自专栏恰童鞋骚年

《你必须知道的.NET》读书笔记一:小OO有大智慧

此篇已收录至《你必须知道的.Net》读书笔记目录贴,点击访问该目录可以获取更多内容。

582
来自专栏机器之心

教程 | PyTorch内部机制解析:如何通过PyTorch实现Tensor

选自Github 机器之心编译 参与:朱乾树、黄小天 PyTorch 中的基本单位是张量(Tensor)。本文的主旨是如何在 PyTorch 中实现 Tens...

4245
来自专栏calmound

散列表

http://blog.csdn.net/yyxaf/article/details/7527878 搜索关键词:散列函数、散列表、哈希函数、哈希表、Hash函...

37912
来自专栏Crossin的编程教室

【编程课堂】有序字典 OrderedDict

编程课堂将和每周一坑一样,成为本教室公众号的一个长期固定栏目。每期讲解一个编程知识点,包括但不限于 Python 语法、模块介绍、编程小技巧等。用简短的篇幅,让...

3728
来自专栏李海辰的专栏

Unity引擎资源管理代码分析 ( 2 )

上一篇《Unity 引擎资源管理代码分析 ( 1 ) 》讲解了 Unity 引擎资源管理代码的类型设计架构和 Resources.Load 接口的实现,本文将...

9004
来自专栏java一日一条

最全面的 Android 编码规范指南

这份文档参考了 Google Java 编程风格规范和 Google 官方 Android 编码风格规范。该文档仅供参考,只要形成一个统一的风格,见量知其意就可...

1624
来自专栏逍遥剑客的游戏开发

C++的反射和序列化

1612

扫码关注云+社区

领取腾讯云代金券