Object-oriented infrastructure design

xiaoxiao2021-03-05  32

Object-oriented infrastructure design

A game engine is a huge and complex software system. Object-oriented software engineering and class design methods can provide good support to such large software systems. This appendix will provide a review of the basic problem for object-oriented structural design. In addition, some object-oriented problems involved in the game engine design will focus on, including naming rules, name domain, runtime type identification, type identification of runtime, alone or multiple inheritance, template (parameters Data type, generic?) Public object, reference count, stream processing, start and closing mechanism.

A. 1 Object-Oriented Software Construction

Published in the 1988 Meyer papers is an excellent reference for object-oriented software engineering, a deep and extensive reading of the stack, string, list, queue, mapping, collection, tree, and diagrams these abstract data types is published in Booch in 1987 (estimated is a forum abroad)

A.1.1 Software quality

The goal of software engineering is to improve software quality from both users and programmers. The expected software quality is in the following two aspects

■ From the outside: The software is fast enough, and it is easy to use. The final user value these qualities. End users include team members who will use code, so it is very important.

■ Internally: Software is readable, modular, structured, programmers pay great attention to these qualities.

External quality is more important because the final goal of software construct is to facilitate customer needs. However, internal quality is the key to improving external quality. Object-oriented software design is to handle internal problems, but eventually will have a great benefit to the external issues below.

■ Correction: The performance of the task determined by software accurately performs demand and specification.

■ Quality: A software handles a variety of properties including non-normal case input.

■ Scalability: The software can make certain changes.

■ Reuse: A program can be reused by the new software part or all of the reuse.

■ Compatibility: Software and other software collaboration performance.

■ Efficiency: For example, the use of hardware resources for processors, memory, and external memory is actually the trade-off of space and time.

■ Portability: Software can be transferred to the capabilities on other hardware or software platforms.

■ Testability: The software can be detected by the test code.

■ Integrity: Software System protects its different parts from all kinds of unauthorized reading and writing, regardless of this operation is intentionally or unintentional.

■ Easy to use: The convenience of the software that is used. Learning content includes executing procedures, ready to enter data, interpret output data, save abnormalities.

Software Maintenance is a corrected current code, strengthen its performance, expand its code to handle new problems, the following is a representative software maintenance time classification list (Meyer 1988):

N Due to the user's demand change accounting for 41.8%. There is no suspense, which is due to lack of scalability.

The change in N data format accounts for 17.4%. Nothing, this is due to the changes in research data due to the initial design.

Noth abnormal position: 12.4%.

n Routique detected 9.0%. Since special circumstances need to be resolved. Software is also running under normal circumstances.

n Hardware Changes (6.2%) Changes in the standardization of the code related to the hardware can minimize these packages. These standardization mainly powdered hardware powers by hardware-related code.

A.1.2 Module

The module is atomic, consistent, strong, organized. This is not able to truly define the module, but we all know what he is. The following rules will help you determine which software constructor is more modular. n Decomposable: Design method must be helpful, a problem is broken down into several sub-problems, and each child is independently solved.

Ø Example: Self-top design

Ø Reverse example: Initial module

N Combination: This design method supports the element of the software product to constitute a new product any combination.

Ø Example: Mathematical Library

Ø Inverse example: combined GUI and database function library.

n Apprecience: The module can be easily read and reorganized into a design method of new modules.

Ø Example: A mathematical library has many export features, but there is no other import connection file.

Ø Example: Serialized non-independent module, module A relies on module B, module B relies on module C, module C needs. . . . .

n sustainability. A small change form for determining the problem only results in a change in modules, and changes should not affect the architecture of soft software.

Ø Example: Symbol constant (no code number), the principle of unified indexes.

Ø Reverse Example: Because the data is marked after the data is changed, it is not possible to hide the user's data indication.

n Protective: This design method produces such a structure. The role of this structure is to cause the program to occur only in one or two modules under abnormal conditions.

Ø For example: the confirmation of input and output is the concept of pre-processing and post-processing in the abstract data type.

Ø Reverse example: Iruular abnormalities, an exception generates another code module processing by a code module, and another module may be remote, but this mechanism violates the rules that restrict abnormal conditions.

Five rules lead to five rules used to ensure modularity. The rules assured by each rule point out in the parentheses of the climate.

n Language Module Unit: The module can be combined like a syndrome of the sentence in the language. (Decomposition, Combination, Protective)

n less interface, each module interacts as little as possible (continuity, protective new)

n Small interface, if the two modules must communicate, their exchange information must be as small as possible, this is called: low coupling (continuity, protection)

n Clear interface: Two modules must be clearly separated from the modules whenever communications, which is called direct coupling. (Decomposition, Combination, Continuity, Understandable)

n Information Hide: All information about the module must be privatized unless this information is announced.

Open closed principles

This is a final requirement of a module decomposition, which means that a module must be closed and open.

n Open Module: This module is scalable, for example: it can add a new domain to the existing data structure or add a new operation to the existing data structure.

n Closed Module: This module is used by other modules. This requires another way to define a perfect stable interface, especially in terms of stability, for example: such a module must be written as a library.

咋 看: Openness and closure inevitable, if the common interface of a module is consistent, but inside is changed, this module can be considered to be open and closed. However, the best correction is to increase new features, the concept of inheritance is the best way to close and open. A.1.3 Reuse rules

In software engineering, reusability is a basic problem, why there are so many time designs and code systems, but in fact, it may have already existed else. This problem does not have a simple answer. Already written, dealing with stacks or other basic data structural operation code is easy to find, but this problem often contains other factors, some companies provide the library you need, but you want to use it. Authorization, if there is a problem with the provider's code, you have to find themselves to change, this may be not thought about.

At least in your local environment, you can try to maximize your own code module, and some problems are that the data modules must be able to generate reusable units.

N Data Types of Data: The module must apply to various types of data organizations, the model, or parameterized data types can help you solve this problem.

n Data structure and mechanism changes: A set of mechanisms may depend on its underlying data structure, and the module must be able to handle different underlying data structures. The overload can help you solve this problem.

N-related conventional operations, modules must have interfaces for processing underlying data structures.

n Agent Independence: A module must be able to make a user use its operation but do not need to know its internal implementation mechanism and the underlying data structure. E.g:

X_IS_IN_TABLE_T = Search (x, t);

This is a function call for X to look for X in the T form, and its return value is Boolean. If the table to be found may be multiple types (for example: linches, trees, files, etc.), we hope not to have a large number of control structures below:

IF (t is of a)

Apply Search Algorithm A

ELSE IF (t is of type b)

Apply Search A Algorithm B

Else if ... ..

In the module or in the customer code, the overload and polymorphism can solve this problem.

The public part of the N sub-module is very important! Avoid repetition of similar code, because when a small change occurs in this similar code block, all similar code blocks will change, which will make a lot of time to maintain, and an abstract interface is not exposed. Its internal data structure.

A.1.5 function (or process) and data

Functions and data, first consider that? The key point to answer this question is on the scalability of the software, the specific saying: is continuity. In the entire life cycle of the software, because the demand of the system is regular, the function changes is relatively. However, the data operated by the function should be consistent, and the change is very small. This is an object-oriented method for designing a module based on object.

A classic design pattern is the designer design method. First determine the abstract function of the system, then decompose step by step, make the function gradually be easy to implement, this method is logical, better organization. Incentive programmers. But there is also a disadvantage, the specific:

n This method ignores the nature of software to develop. Therefore, there is a problem in continuity, and the top-down design method provides convenience in a short period of time, but once the system changes to the whole, it has left a long-term disaster.

The concept of the N software system is implemented by a function. This is not suitable. For example, the operating system is a typical example that cannot be implemented by a function. The real system is not top. n This design method does not provide any reusability, and the designer is generally based on the existing requirements decomposition function, and the subsystem can only correspond to the existing system. When the system changes, the subsystem cannot be used.

A.1.5 Object-Oriented Design

Object-oriented design results in systems and subsystems of the architecture of object-oriented software rather than functions. The main cries are as follows:

n How to find an object. An organized software system can be seen as an operable model or a part of a whole. The software object only represents a real world object.

n How to describe an object. Describe the standard method of the object is an abstract data type, a definition of an abstract data type includes a type (which is a parameter of an abstract data type), method (operation can be provided by the object), pre-processing (this must be performed before all operations) ), Post-processing (executed after all operations) public rules (behavioral rules for methods).

Object-oriented design usually also views the construction of a software system as a structured collection that is implemented by an abstract data type. That is:

n Based on the object-based structured module, the system is modular on the basis of the data structure.

n Data abstraction. Objects can be described as an implementation of an abstract data type.

n Auto Storage Management: Useless objects are automatically destructed by the underlying language system without the need for programmers.

Class: Each non-simple type is a module, each advanced module is a type, which is considered an example of a class of modules.

n Inherit: A class seeing is defined as an extension or limitation of another.

n Parameterization and dynamic binding, program entities can involve multiple classes, one operation can have different implementations in different classes.

n Multiple and multi-level inheritance, you can define a inheritance class for many categories, or give a class definition multiple inheritance classes.

Whether a language supports these factors or a problem. Specific lectures: Smaltalk and ADA are all of the above factors, however they don't work, this book is the object-oriented code is C , but it is not a full-scale object, C is very good, in need Efficiency is a great convenience, and a public C fallacy is its efficiency but cannot be compared with C ratio. Remember a compiler is a huge software system and is also sensitive as all other large systems, and the realization is often not good. The C compiler of this generation can produce compact and fast code (refer to Ellis and Stroustrup C reference book, 1994). Lippman's Example Extension Set (1991) shows the wonderful features of C .

A.2 Code style, named tradition and namespace

A goal of software engineering is that the code must be readable. In a large software development environment in a lot of programmers, each programmer wants to use its own set of code style, including identifier named, space The style of the front and rear scales, the position of the flower brackets, and more, if a group of code is to see the members in the group, but also to see customers outside, then I suggest you better make your code to keep your code completely style of. The code is not consistent with the customer's requirements, your customer is to understand your code, then use it to his own program. The code style from the management force specified is a choice, but it is necessary to consider the disputes of everyone's opinion. There are currently a lot of C programmers, are currently c, at that time, their code style is fixed. But there are many habits that are inconsistent with object-oriented ideas, these programmers have used their style to learn. Naming tradition is very important, especially when a code reader wants to get what he wants to get from a multi-file code written by multi-programmers. A very effective name tradition is used in the code included in this book, which allows the reader to distinguish member name, local variable name, global variable name, including it is static, so that you It is easy to find the definition and scope of the corresponding variable, and the name information is included in the name of the identifier. Embedded information is not as long as Microsoft's Hungarian nomenclature. But for the reading and understanding of the code: those already very effective.

As a game engine: like other large libraries, inevitable should be integrated with other team software libraries, so there is a conflict that may result in class names or global variable names. This is likely to happen: You define your own matrix class: Matrix, but others may define such a name, C namespace can solve such problems, this method is very in the process of multi-library collaboration General, add a unique prefix in front of the class name and global variable. The structure of the namespace implicitly destroyed the class name, and the prefixers handled more specific to this destruction.

The codes of the code comes with the book are like this: class names and global variable names are prefixed by MGC. The function name is the first letter. If multiple words make a name, each word is capitalized, for example: setting a class representative string, a member function that acquires its length is called getLength. The identifier name is also named Use the same rule, but there is a prefix. Non-static data members are prefixed by m_, and static data members are prefixed in MS_. That M represents member (MEMBER), S represents static (static), a static local variable is started with S_, a global variable starts with G_, a global static variable starts with GS_, and the variable is also considered Insi, it is also a prefix of the laser but is behind the next line with the member or global variable. Table A, a list of different type name code rules, the general mark name is not used, in addition to the prefix in front of it, the typical amount is the uppercase letter, and may include underscore, various coding rules are Combined with, as follows:

Unsigned int * auiarray = new int [16];

Void Reallocarray (int iQuantity, unsigned int * & rauiarray)

{

DELETE [] RAUIAARRAY;

Rauiarray = new unsigned int [iQuantity];

}

Short svalue;

Short & RsValue-Svalue;

Short * psvalue = & svalue;

Class MgcsomeClass

{

PUBLIC:

Mgcsomeclass ();

MgcsomeClass (Const mgcsomeclass & rkobject);

protected:

ENUM {Nothing, Something, Something_ELSE};

Unsigned int m_esomeflag;

Typedef enum {zero, one, two} counter;

Counter M_ECounter;

}

The naming rules not listed in the table below can be derived from the original file.

Type Code Type Code char c unsigned char uc short s unsigned short us int i usigned int ui long l usigned long ul float f double d pointer p smart pointer sp reference r array a enumerate type e class variable k template t function ponier o void v Table A.1 Various types of identifier naming rules

A.3 Running Dynamic Information

Polymorphism provides an abstraction of function functions, a polymorphic function does not need to consider the real type of call objects, but you need to know the type of parameterized object, or you need to decide whether this type stems from a certain type, such as one The chicken rib pointer points to a subclass, which is called dynamic type mapping, and real-time type information provides a method of determining this information at the time of program runtime.

A.3.1 System of single inheritance hierarchy

A single inherited object-oriented system has a direct tree, in which the node represents the class, the edge represents inheritance relationship, assuming that the node V0 represents class C0, the node V1 represents class C1, if C1 is the subclass of C0, Then there is one side between V0 and V1, representing the inheritance relationship between C1 and C0, which represents the relationship between IS-A between the father and son, and Figure A.1 shows a simple Hierarchical relationship inherited.

The root of the tree is a polygon. The rectangle is a polygon. It is a rectangle. It is also a polygon. It is a polygon. The equilateral triangle is a triangle, and the non-equilate triangle is also a polygon, but the square is not a triangle, non-equilate triangle Not an equilateral triangle.

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Figure A.1? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

A runtime information system is an implementation of such a tree. The basic runtime information data type stores a program that may be needed at runtime and stores a base class connection, and the procedure determines whether a class is inherited. In another class, this simplest representation does not store the information of the class, only one connection to the base class, however, this is indeed very useful, although only a string name is stored, this character is actually The string will be used in the flow system described later, which is very useful in the process of quick determination classes in the future DEBUG.

Class Mgcrtti

{

PUBLIC:

Mgcrtti (Const Char * AcName, Const Mgcrtti * Pkbasertti):

m_kname (acname)

{

m_pkbasertti = pkbasertti;

}

Const mgcrtti * getBasertti () const {return m_pkbasertti;}

Const mgcstring & getname () const {return m_kname;}

Private:

Const mgcrtti * m_pkbasertti;

Const mgcstring m_kname;

}

The base class mgcobject in a inherited hierarchy must contain basic support to the RTTI system, and its minimum mechanism is as follows:

Class Mgcobject

{

PUBLIC:

Static const mgcrtti ms_krtti;

Vritual const mgcrtti * getrtti () Const

{

Return & MS_KRTTI;

}

Bool ISExactlyclass (const mgcrtti * pkquryrtti) const

{

Return (getrtti () == pkqueryrtti);

}

Bool ISExactlyclass (const mgcrtti * pkquryrtti) const

{

Return (getrtti () == pkqueryrtti);

}

Bool isderivedfromclass (const mgcrtti * pkquryrtti) const

{

Const mgcrtti * pkrtti = getrtti ();

While (pkrtti)

{

IF (pkrtti == pkqueryRTTI)

Return True;

Pkrtti = pkrtti-> getBasertti ();

}

Return flase;

}

Void * Dynamiccast (const mgcrtti * pkqueryRTTI)

{

Return (IsderiveDFromClass (PKQueryrtti)? THIS: 0);

}

}

Each subclass in the inherited tree structure has a static MGCRTTI and its minimum structure is as follows:

Class MgcderivedClass: Public MGCBaseClass

{

PUBLIC:

Static const mgcrtti ms_krtti;

Virtual const mgcrtti * get rtti () const

{

Return & MS_KRTTI;

}

}

Whether mgcbaseclass is there, or what is inherited, pay attention: The unique indication is ok, because static MGCRTTI members have their own different runtime memory addresses, so the original file of the derivatives must contain:

Const mgcrtti mgcderivedclass :: ms_krtti ("mgcderivedclass",

& Mgcbaseclass :: ms_krtti;

A.3.2 Multi-inherited level system

A non-inherited object-oriented multi-inheritance system can consist of a non-cyclic chart, representing the node in this chart, represents the inheritance relationship, assumes that the node VI represents class CI, for i = 1, 2, 3, if C2 inherits At C1 and C0, there is two edges to represent the relationship between V1 and V0 to represent this multi-inheritance. Figure A.2 shows a multi-inheritance hierarchy, a multi-inheritance of the runtime information system is an implementation of this direct non-inheritance icon relationship. The runtime information system of the single inheritance system has a pointer to the base class, while the multi-inheritance system requires a list of pointers to all base classes. The simplest information is not stored, only the pointer pointing to the base class, in order to support the number of criteria, the omitting number of the C-style is used in the constructor, and therefore requires the standard parameter support, for most compilation In order to include a macro command that provides operational parameters parsing this file for stdarg.h. -------------------- Figure A.2 -------------------------- - ??????????????????

Class Mgcrtti

{

PUBLIC:

Mgcrtti (const char * acname, unsigned int UINUMBASECLASSES .....);

m_kname (acname)

{

IF (uinumbaseclasses == 0)

{

m_uinumbaseclasses = 0;

M_apkbasertti = 0;

}

Else

{

M_uinumbaseclasses = uinumbaseclasses;

M_apkbasertti = new const mgcrtti * [uinumbaseclasses];

VA_LIST LIST;

VA_START (List, UinumbaseClasses);

For (unsigned Int i = 0; I

M_apkbasertti [i] = VA_ARG (List, Const Mgcrtti *);

VA_END (LIST);

}

}

~ Mgcrtti ()

{

delete [] m_apkbasertti;

}

Unsigned int getNumbaseClasses () const

{

Return M_uinumbaseClasses;

}

Const mgcrtti * getBasertti (unsigned int uiz) Const

{

Return M_apkbasertti [uiindex];

}

Private:

Unsigned int m_uinumbaseclasses;

Const mgcrtti ** m_apkbasertti;

Const mgcstring m_kname;

}

The root of the single inherited hierarchy system provides a member function, which is used to search for the detected tree to determine whether a class and another class are inheritance relationship, there is a technical problem in the multi-inheritance hierarchy tree. It is possible to have no side of the node, that is, this inheritance will have multiple roots. In order to solve the problem, it will provide a single root class, which provides an interface for any system in this inheritance.

The root device structure in multi-inheritance system is very similar to the root structure of single inherited trees, except for the implementation of the member function, isderivedFromClass is used to process the base class pointer list of RTTI

Bol Mgcobject :: IsderiveDFromClass (const mgcrtti * pkqueryrtti) Const

{

Const mgcrtti * pkrtti = getrtti ();

IF (pkrtti == pkqueryRTTI)

Return True;

For (unsigned int i = 0; i getNumbaseClasses (); i ) {

IF (ISDeriveDFromClass (Pkrtti-> GetBasertti (i))))

Return True;

}

Return False;

}

The base class still provides the same static member RTTI function to operate a member function of its class address, for example, consider the following example:

Class Mgcderived :: public mgcbase0, mgcbasel

{

PUBLIC:

Static const mgcrtti ms_krtti;

Virtual const mgcrtti * getrtti () const

{

Return & MS_KRTTI;

}

}

MGCBase0 and MGCBase1 or objects of Mgcobject classes, or derived from mgcobject, derived from this type of source file must contain

Const mgcrtti mgcderived :: ms_krtti ("mgcderived", 2, & mgcbase :: ms_krtti, & mgc_krtti, & mgcbase1 :: ms_krtti);

A.3.3 Macro Support

The process can be effectively solved with the lengthy problem of the code in the program, and this macro can be provided to the single inheritance and multiple inheritance systems.

Macros in mgcrtti.h

#define mgcdeclarertti /

PUBLIC: /

STATIC: /

Static const mgcrtti ms_krttti; /

Virtual const mgcrtti * getrtti () cosnt {returnium & ms_krtti;}

#define mgcimplementroottti (rootclassname) /

Const mgcrtti rootclassname :: ms_krtti (# rootclassname, 0)

Macros in mgcObject.h and mgcobjectm.h

#define mgcisexactlyclass (Classname, POBject) /

POBJECT? POBJECT-> ISEXACTLYCLASS (& classname :: ms_krtti): flase)

#define mgcisderiveDFromClass (ClassName, POBject) /

POBJECT? POBJECT-> IsDeriveDFromClass (& classname :: ms_krtti): false)

#define mgcstaticcast (Classname, POBJECT) /

(ClassName *) POBEJCT)

#define mgcDynamiccast (classname.pobject) /

POBJECT? (ClassName *) POBject-> Dynameiccast (& classname :: ms_krtti): 0)

Macro MGCDeclarertti is placed in the class declaration of the header file, paying attention: The life domain is public, so any other class declaration following this macro is needed. If necessary, you need to define other life domains.

Below this is a macro that provides to the single inheritance system:

#define mgcimplementrtti (Classname, BaseclassName) /

Const mgcrtti classname :: ms_krtti (# classname, & baseclassname :: ms_krtti);

And it must be used in the original file defined, for multi-inheritance systems: Such macros is impossible because the C-style macro does not allow a large number of parameters.

A.4 template

Templates, sometimes called: parameterized data types for sharing code between classes with the same structure. The typical example of this is the stack of the object, for a limited stack: the operation of it includes a stack (PUSH), out of the stack (POP), determining whether it is empty, it is full (isfull) And read the top elements (read stack top elements but do not make it out), what types of operations and stack storage objects are not related, and a stack can be implemented for storing integers and floating point, each using trees Save storage stack elements, uniquely different stacks use an integer tree group, while floating-point stacks use a floating-point tree group. The use of the template can cause the compiler to generate object code according to the type of program requirements. Template Calss Stack

{

PUBLIC:

Stack (int tentacksize)

{

m_istacksize = sstacksize;

m_itop = -1;

m_akstack = new t [istacksize];

}

~ Stack () {delete [] M_AKSTACK;

Bool Push (Const T & RkelEment)

{

IF (m_itop

{

M_AKSTACK [ m_itop] = rkelEment;

Return True;

}

Return False;

}

Bool Pop (T & Rkelement)

{

IF (m_itop> = 0)

{

RkelEment = m_akstack [m_itop--];

Rreturn True;

}

Return False;

}

Bool Gettop (T & Rkelement) Const

{

IF (m_itop> = 0)

{

RkelEment = m_akstack [m_itop];

Return True;

}

Return False;

}

Bool isempty () const {return m_itop == - 1;}

BOOL ISFULL () const (return m_itop == m_istacksize-1;}

protected:

Int m_istscksize;

INT M_ITOP;

T * m_akstack;

}

Macros can produce multiple types of code, but it is not sensitive to the resulting side effects. Although it can also achieve two types of stacks that support shaping and floating-point, it will have problems in the maintenance of the code. If a file changes, other files will change, when there is a lot of type sharing When the code, this problem will also exacerbate, the template provides a way to change these changes in a file.

The template provides a nice selection for the stack, linked list, and array these abstract data types, and the standard template library can be integrated into a game engine. When it is handled, it must be clear when a problem is processed. It will inevitably have a certain boundary effect, especially when it constructs and destructure. If a standard template library class object is to change its size, it will apply for a set of storage spaces, in which the old container class content is copied, and then the original memory is released. This mechanism has an implicit assumption: the underlying data is local. If the data is a class object, this memory replication causes memory leaks, and the Side Effect will happen! This is very clear for the shared object and reference count of the next section. If the standard template library does not support these boundary effects, the game engine needs to implement its own container type. A.5 Shared object and reference count

In the game engine, the sharing of the object is natural. After the model sharing of a large amount of data, the rendering state can also be shared, and the texture picture is shared between the rendering body. In a game, if you manage a shared object by manual, you will inevitably lead to some objects (object leakage) or destructed some objects being used, so a more automatic shared object management is shared. The custom management of the object, most popular systems add a reference counter to the root object, an object is referenced once by another object, and its reference counter adds 1, once the reference counter is reduced to 0, this object is in the system The class will no longer be referenced, so it is deleted.

The details of the reference counter can be disclosed, so that the program of the reference counter is adjusted to be responsible. This mechanism adds great confidence to the programmer's correct management object. Another option is to implement a reference counter in an intelligent pointer system zone internally to facilitate interference when the program needs special processing. Therefore, for programmers: The burden of management objects is greatly reduced!

In addition to real-time operation information, root MGCObject now includes the following code to support reference count:

PUBLIC:

Mgcobject () {m_uireference = 0; MS_UITATALOBJECTS ;

~ Mgcobject () {ms_uitotalobjects--;}

Void incrementReference () {m_uireferences ;}

Void DecrementReference () {if (- M_UIREFERENCES == 0) delete this;}

Unsigned int getreferneces () {return m_uireference;}

Statci Unsigned Int GetTotalObjects () {Return MS_UITOTALOBJECTS;

Private:

Unsigned int m_uireference;

Static unsigned int ms_uitotalobjects;

The static counter tracking object is now in the system, the initial value of the program execution is 0.

The intelligent pointer system is implemented throughout the basis and uses templates.

Template

Class Mgcpointer

{

PUBLIC:

// construction and destruction

Mgcpointer (t * pkobject = 0);

Mgcpointer (Const Mgcpointer & rkpointer);

~ Mgcpointer ();

// Implicit Conversions

Operator T * () const; t & operator * () const;

T * Operator-> () const;

// Assignment

Mgcpointer & Operator = (const mgcpointer & rkreference);

Mgcpointer & Operator = (t * pkobject);

// commitarisons

Bool Operator == (t * pkobject) const;

Bool Operator! = (t * pkobject) const;

Bool Operator == (const mgcpointer & rkreference) const;

BOOL Operator! = (const mgcpointer & rkreference) const;

protected:

// the Shared Object

T * m_pkobject;

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

Template

Inline mgcpointer :: mgcpointer (t * pkobject)

{

m_pkobject = pkobject;

IF (M_PKOBJECT)

m_pkobject-> incrementReference ();

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

Template

Inline mgcpointer :: mgcpointer (const mgcpointer & rkpointer)

{

m_pkObject = rkpointer.m_pkobject;

IF (M_PKOBJECT)

m_pkobject-> incrementReference ();

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

Template

Inline mgcpointer :: ~ mgcpointer ()

{

IF (M_PKOBJECT)

m_pkObject-> DecrementReference ();

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

Template

Inline mgcpointer :: operator t * () const

{

Return M_PKOBJECT;

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

Template

Inline T & Mgcpointer :: Operator * () const

{

Return * m_pkObject;

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

Template

Inline T * mgcpointer :: Operator-> () const {

Return M_PKOBJECT;

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

Template

Inline mgcpointer & mgcpointer :: operator = (const mgcpointer & rkpointer)

{

IF (m_pkobject! = rkpointer.m_pkObject)

{

IF (M_PKOBJECT)

m_pkObject-> DecrementReference ();

m_pkObject = rkpointer.m_pkobject;

IF (M_PKOBJECT)

m_pkobject-> incrementReference ();

}

Return * this;

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

Template

Inline mgcpointer & mgcpointer :: operator = (t * pkobject)

{

IF (M_PKObject! = pkobject)

{

IF (M_PKOBJECT)

m_pkObject-> DecrementReference ();

m_pkobject = pkobject;

IF (M_PKOBJECT)

m_pkobject-> incrementReference ();

}

Return * this;

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

Template

Inline Bool Mgcpointer :: Operator == (t * pkobject) const

{

Return (m_pkobject == pkObject);

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

Template

Inline Bool Mgcpointer :: Operator! = (t * pkobject) const

{

Return (M_PKObject! = pkobject);

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

Template

Inline Bool Mgcpointer :: Operator == (Const Mgcpointer & RKPointer) Const

{

Return (m_pkobject == rkpointer.m_pkObject);

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

Template

Inline Bool Mgcpointer :: Operator! = (const mgcpointer & rkpointer) const {

Return (M_PKObject! = rkpointer.m_pkobject);

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

The assignment operator must compare the value referring to the pointer before the adjustment reference count is required to prevent illegal operations.

Mgcpointer sppointer = newmgcobject;

Sppointer = SPPOINTER;

MGCObject's constructor setting reference is 0. mgcpointer constructor setting count 1. If the initial control does not appear in the assignment operator, the reference count of DecrementReference will be reduced to 0, then this object is deleted. . Thus, each pointer rkpointer, M_PKObject is pointing to a memory that is not a program, and the structure of assignment will make the pointer to illegal memory blocks, and the invition of IncrementReferences will write to an illegal memory block, although such errors are in the program Not necessarily, but this situation may exacerbate due to the confusion of the pointer.

For convenience, MGCObject or other classes that are inherited by it will avoid the length of the template symbol, support the macro that defines the smart pointer is:

#define mgcsmartpointer (classname) /

Class classname; /

Typedef mgcpointer classname ## Ptr

For the convenience of the customer code, each class can put this definition in its header file, for example: mgcobject.h will include mgcobject and its status definition

MgcsmartPointer (Mgcobject);

This determines the type of MgcObjectPtr. This forward definition of the name names in the macro supports the forward definition of the intelligent pointer type.

It may be necessary to construct a smart pointer pointing to a pointer or smart pointer, such as: class mgcnode, which is the internal agent of the scene diagram, which inherits from Mgcspatial, page node proxy scene graphics, multi-state allowed assignment:

Mgcnode * pknode = ;

Mgcspatial * pkobject = pknode;

In theory: The smart pointer of the MGCNodePtr type inherits from MgcspatialPtr, but the language does not support this, but inherits, inherits, in the intelligent pointer, the internal implicit operator conversion allows such an incidentally, for example::

// this code is valid.

Mgcnodeptr spnode = ;

Mgcspatialptr spobject = spnode;

// this code is not valid when class a is not derived from Class B

Mgcaptr spaobject = new a;

Mgcbptr spbobject = spaobject;

This implicit conversion also supports the comparison of intelligent pointers and empty pointers, just like a routine pointer:

Mgcnodeptr Sonode = ;

Mgcspatialptr spchild = spchild-> getChildat (2);

IF (SPCHILD)

{

;

}

Below is an example of using and emptying the intelligent pointer. This MGCNode class stores an array of intelligent pointers for its subclasses.

Mgcnodeptr spnode = ;

Mgcnode * pknode = new mgcnode; // pknode references = 0

Mgcnodeptr spchild = new mgcnode; // pknode references = 1

Spnode-> attchchild (spchild); // pknode references = 2

SpNode-> DetchIld (spchild); // pknode references = 1

SPCHILD = 0; // pknode references = 0 // desroy it

This shows how to properly end a smart pointer, which is very good in this code to delete spchild. If the object pointing to SPCHild has a positive reference count, clearly call the destructor to force deletion, another object to the same object has a suspended pointer, if the intelligent pointer operation is replaced, the reference count is reduced If there is another object to point to this object, the object refers to not being destroyed, so the code like this is safe:

Mgcnodeptr spnode = ;

Mgcnode * pknode new mgcnode; // pknode references = 0

Mgcnodeptr spchild = new mgcnode; // pknode references = 1

Spnode-> attachchild (spchild); // pknode references = 2

SPCHILD = 0 // pknode references = 1,

// no destruction

Note: This coded species did not assign a value of 0 to the smart pointer. The destructor of this intelligent pointer was called, and the reference count of PKNODE was still reduced to 0.

When using a smart pointer, these additional rules must also be insisted. Smart pointers are only available to dynamic objects. Can't point to the object stack. E.g:

void myfunction ()

{

Mgcnode knot; // knode references = 0

Mgcnodeptr spnode = & knot; // knode references = 1

SpNode = 0; // knode references = 0

// knode is deleded

}

This action is destined to fail, because Knode is implied in the stack, the object is not implied in the stack, and the object is not in the heap.

Use the smart pointer as a function parameter or the function return value is also defective, the following example illustrates this danger:

Void myfunction (mgcnodeptr spnode)

{

;

}

Mgcnode * pknode = new mgcnode;

Myfunction (pknode);

// pknodenow points to invalid memory

There are 0 references when sending the memory to the pknode, and MyFunction is called by the class's replication constructor adds an instance of MGCNODEPTR in the stack. When returned from the function, the instance of this MGCNODEPTR is destructed in this process. PKNODE has 0 references, which will also be destroyed, so the following code is safe: mgcnode * pknode = new mgcnode; // pknode references = 0

Mgcnodeptr spnode = pknode; // pknode references = 1

MyFunction (spnode); // pknode references increase to 2,

// then decrease to 1

// pknode references = 1 at this point

The relevant issues are as follows:

Mgcnodeptr myfunction ()

{

Mgcnode pkreturnNode = new mgcnode; // reference = 0;

Return PkreturnNode;

}

Mgcnode * pknode = myfunction ();

// pknode now Points to Invalid Memory

Through the compiler, an instance of a temporary MGCNodePtr as a function returned value is implicitly generated. Since the copy constructor generates this instance, the reference count of pkNode is 1, which is no longer needed below, which will be implicitted, and the reference count of pknode changes to 0, so it is also destructed. The following code will be safe.

Mgcnodeptr spnode = myfunction ();

//spnode.m_pkObject Has One Reference

This temporary example adds a reference count of pkreturnNode to 1. The copy constructor is used to make a SPNode node, so the reference count is added to 2, so when the temporary object is destructed, the reference count is still 1.

A.6 stream processing technology

The game engine needs to store consistency. The content of the game is generally a format that must be imported by a modeling tool. The game program itself needs to store its own data to re-read after a period of time. The streaming of data means that the process of mapping data between the media is typically between the hard disk memory and the memory, in which part we will discuss data transfer between memory and hard disks, but this method is Provides the transmission of the memory block (and supports data transfer through the network) A class of processing streams is generated --- MgcStream.

A.6.1 Data Storage

The data typically stored on the hard disk is scene graphics data. Although a scene map type can be traversed when visiting and stores the object in a scene, there is two complications. One aspect is: Some objects can be shared by different graphics that can be different from the scene. In this case, an object is likely to be stored twice. On the other hand: The object may contain a pointer to an object to you. This happens mainly in the sonic relationship of the node in the scene map, in detail, is: a stored scene map type must be able to re-enter the memory, and all of these objects must be readable. Abstract look at this question: It is a scene graphic is an abstract map of an object (type is mgcobject) (estimated to refer to the data structure, the translator is pressed), the node in the figure represents the pointer relationship between the arc representative objects. . Each object has an abstract member, and the specific say is the local data type (shaping, floating point, string, etc.). This picture must also be stored on the hard disk so that you can be modified later. This means that nodes and arcs in the figure must be stored in some reasonable form. Moreover, each figure can only be stored once, and the process of storing such a scenario is stored and generated and stored in a picture in the object linked list to the hard disk is equivalent, and all the relationships between the objects are also stored. If there is a multi-connected unit in the figure, each unit will also be traversed, and support for multiple abstract objects is easy to implement. Class MGCStream can generate a linked list for a top-level storage object, which is: This is the root element of some scene graphics, but it may be a status attribute that needs to be stored in other objects. In order to support reading of this file, and to an identical graph of the same top element, an information tag block must be written before the abstract graph related to each top element is written into the hard disk. A simple method is to write a string. Mark the number of different objects in the traverses and to bring each access to the object to the access to the access to the object linked list, for the speed of I / O, an ideal data structure is a hash table, Traverse the map, still can build a Haxi table, visit the Haxi table, just like accessing the list, then save the data of the object into the hard disk. In order to support reading, the runtime type information of the object is first written, in order to support the fast reading and writing of the file block, then written is the number of BYTEs stored. Although not all data needs to be written, any local data type is handled by standard C stream operator. Object member is not a local data type or is not a MGCObject class using their own stream operator processing, some data members can be exported by other data members, then means that the objects in the figure are related to members. Sex. This figure is only written in this picture, once reread, the relevant object member can be properly constructed.

An object pointer data member can be stored as an unsigned integer memory address because a memory address is a arc representation in the figure, however, when the stored file is read, this pointer value is no longer The legal memory address, the processing method of this problem is discussed in the next section.

A.6.2 Reading

The process of reading is more complicated than the process of writing. Because the pointer value originally stored on the hard disk is illegal, each object must first be created in memory, then the data read from the hard disk, and then the connection between the objects, such as the parent child must be established. Do not consider illegality of the roll value of the hard disk, and then the runtime type information about the figure is read, the address of each object. Corresponding to a pointer in a hard disk, so the Haxi table that stores different objects can be used to track the relevance between the hard disk pointer value (called link ID) and the dynamic memory address of the object. When all objects are in the middle of the memory, this Haxi table is completed, this Haxi table can be used iteratively access as a linked list, and the link ID of each object will be replaced by the dynamic memory address. This is also a link compiler The principle of the generated OBJ file. Below is the step of reading an object from a stream: dynamic type information is first read to facilitate determining the type of object. Then the size of the file block is read out by both the number of BYTE. All the information required to create an object is now known. A suitable constructor and any setup method is called to create this read object, only relying on runtime type information to determine that the use of that constructor is not enough. Therefore, each class is to provide a static factory mode function, then the MGCStream object holds a Hasix table for such a function; the Hasi table is the runtime information, the function of the factory model acts as a constructor, using already read The memory block of the object generates a correct type of object and initializes this object using the information inside that memory block.

A.6.3 Flow Technical Support:

MgcStream supports the following interfaces in the high level:

Class Mgcstream

{

PUBLIC:

// construction and destruction

MgcStream ();

~ Mgcstream ();

// the Objects to process, Each Object representing an entry Into a

// Connected Component of the Abstract Graph.

BOOL INSERT (Mgcobject * pktople);

Void transovell ();

Unsigned int getObjectcount ();

Mgcobject * getObject (unsigned int UIINDEX) Const;

Bool iStoplevel (Mgcobject * pkobject);

// File Loads and Saves

Bool load (const char * acfilename);

Bool save (const char * acfilename);

//Memory Loads and Saves

Bool Load (Char * Acbuffer, int isize);

Bool Save (Char * & Racbuffer, Int & Risize);

// Linking Support

Class Link

{

PUBLIC:

LINK (Mgcobject * pkobject);

}

}

The MGCTStorage class represents a template size that can become array store classes. In a program, if you want to store a file to your hard drive:

Mgcstream kstream;

For (Each Pkobject Worth Saving)

KStream.insert (pkobject);

KStream.save ("myfile.mff");

KStream.removeallObjects ();

Read a file in a program to memory as follows:

Stream kstream;

KStream.Load ("MyFile.mff");

For (unsigned int UIINDEX = 0; UIINDex

;

KStream.remove AllObjects ();

This RemoveAllObjects () function clears the stream to be used in the future or read the object. The base class MGCObject provides a stack relative to its own streaming.

PUBLIC:

// Support for loading

Static Mgcobject * Factory (Mgcstram & rkstream);

Virtual Void Load (Mgcstream & Rakstream, Mgcstream :: Link * PKLINK);

Virtual Void Link (MgcStream & Rkstream, Mgcstream :: Link * PKLINK);

// 'Support for saving

Virtual Register (MgcStream & rkstream);

Virtual Void Save (MgcStream & rkstream);

The flow save () function iterates all top-level objects and calls the registration method of each object. The first step in this process is to traverse the abstract map and add different objects in the figure into the Haxi table maintained by the flow. After a cascode, the stream object iterates access to the Haxi table, and then calls each object's save () method .

The stream LOAD () method reads the file, and then reads an object with the size of the type information and block size. Static factory mode functions are found through the Hash table and then call. Then this factory mode function creates an object and then calls its load () function, the object pointer and the link ID are in the Haxi table of the stream object. After all objects are read, each object is read, and each iterator is called through the iterator. The LINK function of the function is used instead of the link ID. Any top object in this process is placed in this object of the flow object to facilitate the program to operate them.

A trouble to be processed is: The link ID is consistent in the object being read and the object is on the link. At first, the link ID can be stored in a member MgcObject pointer of the object, but this method is not in the case of the shared object and the smart pointer. If a member has a member of a smart pointer, the assignment of the link ID implicitly enforces the operation of the reference count, because the link ID is not a legal memory address, any call to its member function is not Legal and will be wrong, so the link ID must be stored independently as a rule pointer. The MGCStream class defines a nested class to support this MGCObject link with an index (used to track the object being processed), when an object's factory mode function is called, MGCStream: : Link makes an array and passes it to the Load function. Any link ID is stored in this array. When the Load function of the base class is called, the link ID will contact the flow object in the Sikiki table. together. When the LINK () block is running, the link ID is used by the hull to all Link functions as a replacement of its own alternative-dynamic memory address.

The following pseudo codes will explain how the stream implements this feature, first assume that mgcderive is inherited from MGCBase.

Mgcobjejct * mgcderived :: Factory (MgcStream Rkstream)

{

Mgcderived * pkderived = new mgcderive;

MgcStream :: link * plink = newmgcstreamlink; pkderived-> load (rkstream, plink);

Return pkderive;

}

Void Mgcderived :: Load (Mgcstream & rkstream, MgcStream :: Link * PLLINK)

{

MGCBase :: Load (rkstream, pklink);

// load the mmeber data for 'this' Here.any Mgcobject * Member Arre loaded INTO PLLINK.M_TOBJECT for Use as link IDs.

}

Void Mgcderived :: Link (MgcStream & rkstream, MgcStream :: link * pklink)

{

MGCBase :: LINK (RKSTREM, PKLINK);

// Link He Mgcobject * Member for this 'Here', this is

// generally the complicated part of the process Since Link Resolution Could Require Calling

// Member Function of 'this'to Establish The Connections Between the loading Objects and'this'

Bool Mgcderived :: Register (Mgcstream & rkstream)

{

IF (! mgcbase :: register (rkstream)

{

// 'this'si shared and was alrady registered by Another Owner

Return False;

}

For Each Mgcobject Pointer 'MEMBER'OF' THIS 'DO MEMBER. Register (rkstream);

}

Void Mgcderived :: Save (MgcStreamRKstream)

{

MGCBase :: save (rkstream);

// Save the me. Any mgcobject * members

// have their pointer value des be used as a

// link ids when the file si loading at Later Date

A.7 Open and close

Many classes in the system need to initialize before the program main function and have certain termination operations after the program main function. For example, a matrix class may store a static constant as its initial matrix, and the following code can initialize the static data member before the main function.

// in Matrix.h

Class Matrix

{

PUBLIC:

Matrix (float fm00.float fm01, float fm02,

FLOAT FM10.FLOAT FM11, FLOAT FM12,

FLOAT FM20.FLOAT FM21, FLOAT FM22)

{

// int atialization of m_aafm [] [] Goes here

}

Static const Matrix Identity;

protected:

FLOAT M_AAFM [3] [3];

}

// in matrix.cpp

#include "matrix.h"

Const Matrix Matrix :: Identity (1, 0, 0, 0, 1, 0, 0, 0, 1);

The compiler will generate the Matrix constructor executable before the main function is guaranteed to be ready to wait for a matrix before the program is ready, if you need to initialize the dynamic memory before the program main function, then you have to Release, the following code illustrates an automatic way of C implementation. This mechanism can also be used to use any static data. // in Point.h

Class Point

{

PUBLIC:

Point (Float Fx, Float Fy, Float FZ);

{

m_fx = fx; m_fy = fy; m_fz = fz;

}

Static void intialize ()

{

MS_UIQUANTINTITY = Default_quantity;

MS_AKHANDYBUFFER = New Point [MS_UIQUANTINTITY];

ZERO.M_FX = 0;

ZERO.M_FY = 0;

ZERO.M_FZ = 0;

}

Static void terminate ()

{

DELETE [] MS_AKHANDYBUFFER;

}

Static const point zero;

protected:

FLOAT M_FX, M_FY, M_FZ;

ENUM {Default_quantity = 32};

Static Point * ms_akhandybuffer;

Friend class _pointinitterm;

}

//inpoint.cpp

#include "point"

Const Point Point :: Zero; // Just Declare Storage, No Initialization

Class _pointinitterm

{

PUBLIC:

_Pointinitterm () {Point :: Initialize ();

~ _PointiintTerm () {Point :: Terminate ();

}

Static_Pointerinitterm_forceinitterm;

The compiler will generate a constructor call for _forceinitterm before the main function, and generate a parsing function call after the main function.

This start and closing mechanism is automatic, but there is a correlation between a class that requires remedies, for example: hypothesis that the class A has a static member to be initialized before the main function, and a member of class B must be initialized to from the class. A value of A, the initialization code is in the source file of that class. The compiler handles two source files at the same time, but the code before the generated main program does not support any specific instructions.

// in A.h

Class A

{

PUBLIC:

Static void initialize () {;

Static void Terminnate () {;

Static a iebject;

Private:

Friend class_initterm;

}

// in A.cpp

#inlude "a.h"

A a :: object;

Class_Ainitterm

{

PUBLIC:

_Ainitterm () {a :: initialize ();

~ _AINITTERM () {a :: terminate ();

}

Static_Ainitterm_forceinitterm;

// in b.h

#include "a.h"

Class B

{

PUBLIC:

Static void initialize () {dependent_Object = a :: object;

Static void Terminate () {;

Static a dependent _object; private: PRIVATE:

Friend class_binitterm;

}

// in b.cpp

#include "b.h"

A b :: dependent_object;

Class _binitterm

{

PUBLIC:

_Binitterm () {b :: initialize ();

~ _Biinitterm () {b :: terminate ();

}

Static _binitterm_forceinitterm;

Before the main program: If A :: Object is initialized before the B :: DepennDent_Object is initialized, it will not have problems, but if the order is reversed, then B :: Dependent_Object will be used as a :: Object to prepare memory space In the middle, it will be in the space, because this object is static.

The correlation problem between this class can be solved by positioning. This way makes the initialization operation requiring pre-processed initialization operations, but this method has caused another problem: a class pre- Processing initialization operation can only be called once. The solution is to join a BOOL identifier to indicate whether the initial operation has occurred. If it is called for the second time, it will return directly. Explain as follows:

// in A.h

Class A

{

PUBLIC:

Static void initialize ()

{

Static s_binitialized = false;

IF (s_binitialized) return;

S_Binitialized = True;

;

}

Static void Terminate () {;

STATIC a Object;

Private:

Friend class_initterm;

}

转载请注明原文地址:https://www.9cbs.com/read-34802.html

New Post(0)