Goal: Implement a class function to add object-oriented features to lua
Basics: Unlike compiled object-oriented languages, there is no concept of a class definition in lua, either as a class definition or as an instance of a class, which needs to be emulated through Lua tables.The Lua object-oriented way we implement is prototype, that is, the class is a lua table, which defines the prototype of the class instance, which is another lua table based on this prototype.
Key: Implementing Lua object-oriented can be broken down into two issues: the definition of classes and the instantiation of classes.Classes are defined primarily to implement inheritance, that is, how to make a subclass have a method set of the parent class.Class instantiation requires resolving how instances share a class's method set, but only their own instance of member variables.
Schema: The subclass duplicates the methods of all the base classes at definition time and assigns the class to the instance as u index of the metatable at instantiation time.This is the implementation of the lua class in cocos2dx.
[plain] view plain copy
function class(classname, super) local cls = {} if super then --Copy base class methods cls = {} for k,v in pairs(super) do cls[k] = v end cls.super = super else cls = {ctor = function() end} end cls.__cname = classname cls.__index = cls function cls.new(...) --instantiation local instance = setmetatable({}, cls) instance.class = cls instance:ctor(...) return instance end return cls end
In this implementation, all instances of subclasses share methods from the class prototype.Suppose we define method A in ClassA. When calling method A of an instance, the lua interpreter first looks for method A in the instance table, because we don't add a method to the instance, so we can't find it. The interpreter then looks for method A through the u index field in the metatable of the instance, which is actually the prototype of the class, so method A is found and calledGot it.Now we call method B of the instance, which is not defined in ClassA, but in the base class ClassB of ClassA. Since ClassA has copied all the methods of the base class ClassB once when it was defined, the interpreter can still call B successfully and inherit the implementation.
However, there is a serious problem with this implementation, and member variables of classes are not inherited.Look at the following test:
[plain] view plain copy
BaseClass = class("BaseClass", nil) function BaseClass:ctor(param) print("baseclass ctor") self._param = param self._children = {} end function BaseClass:addChild(obj) table.insert(self._children, obj) end DerivedClass = class("DerivedClass", BaseClass) function DerivedClass:ctor(param) print("derivedclass ctor") end local instance = DerivedClass.new("param1") instance:addChild("child1")
Run this test and we get two lines of output:
[plain] view plain copy
derivedclass ctor bad argument #1 to 'insert' (table expected, got nil)
Why is self._children nil?From the output we can see that the instance of the subclass did successfully call the addChild method of the parent class, but the method call failed because self._children is nil.On closer inspection, we find that the constructor of the base class is not called at all. Our self._children = {} is placed inside the constructor of the base class, and the constructor of the base class that is not called, natural self._children, is nil.
Okay, it looks like this class implementation is not perfect. Let's fix it by calling the parent class's constructor inside the subclass's construction.
[plain] view plain copy
function DerivedClass:ctor(param) self.super:ctor(param) print("derivedclass ctor") end
Running the test again, we get three lines of output:
[plain] view plain copy
baseclass ctorderivedclass ctorclasstest1.lua:32: bad argument #1 to 'insert' (table expected, got nil)
This time the base class constructor was called successfully, why is self._children still nil?
dump out the contents of the instance We find that after calling the constructor of the base class _children is added to the prototype of the base class and not to the instance of our subclass, so finding _children in the self of the subclass instance is naturally missing.
[plain] view plain copy
<1>{ class = <2>{ __cname = "DerivedClass", __index = <table 2>, addChild = <function 1>, ctor = <function 2>, new = <function 3>, super = <3>{ __cname = "BaseClass", __index = <table 3>, _children = <4>{}, _param = <table 1>, addChild = <function 1> ctor = <function 4>, new = <function 5> } }, <metatable> = <table 2> }
The prototype of the base class should not have member variables at all, so this patch is not what we want.Of course, there is no problem if we change this:
[plain] view plain copy
function DerivedClass:ctor(param) self._children = {} print("derivedclass ctor") end
However, if the members defined in the base class are defined in the subclass again, this is not the object-oriented we want.It seems that the lua class implementation of cocos2dx is problematic.
Fix: The class definition part of the above method is fine, but the instantiation part is rough.The self used to call the base class constructor refers to the base class's prototype table, and what we want is to point to the instance table, where the prototype only needs to provide a method, not instantiate member variables, so we thought about making the following modifications.
[plain] view plain copy
function class(classname, super) local cls = {} if super then cls = {} for k,v in pairs(super) do cls[k] = v end cls.super = super else cls = {ctor = function() end} end cls.__cname = classname cls.__index = cls function cls.new(...) local instance = setmetatable({}, cls) local create create = function(c, ...) if c.super then -- Recursive Up Call create create(c.super, ...) end if c.ctor then c.ctor(instance, ...) end end create(instance, ...) instance.class = cls return instance end return cls end
Run the test again and we get the right results.Print out the structure of the subclass instance, and we see that _children is now a member variable of the instance table:
[plain] view plain copy
<1>{ _children = <2>{ "child1" }, _param = "param1", class = <3>{ __cname = "DerivedClass", __index = <table 3>, addChild = <function 1>, ctor = <function 2>, new = <function 3>, super = <4>{ __cname = "BaseClass", __index = <table 4>, addChild = <function 1>, ctor = <function 4>, new = <function 5> } }, <metatable> = <table 3> }
cocos2dx-lua function.lua defines the class method, which makes the inheritance of lua as beautiful and convenient as traditional languages
Look at the definition
[plain] view plain copy
print?
function class(classname, super) local superType = type(super) local cls --If the parent is neither a function nor a table Then the parent class is empty if superType ~= "function" and superType ~= "table" then superType = nil super = nil end --If the type of the parent class is a function or C object if superType == "function" or (super and super.__ctype == 1) then -- inherited from native C++ Object cls = {} --Copy members if parent class is table and set inheritance information for this class --If it is a function type, set the construction method and set the ctor function if superType == "table" then -- copy fields from super for k,v in pairs(super) do cls[k] = v end cls.__create = super.__create cls.super = super else cls.__create = super cls.ctor = function() end end --Set the name of the type cls.__cname = classname cls.__ctype = 1 --The function defining the type of instance creation is the constructor of the base class and copied to the subclass instance --And call the number of children ctor Method function cls.new(...) local instance = cls.__create(...) -- copy fields from class to native object for k,v in pairs(cls) do instance[k] = v end instance.class = cls instance:ctor(...) return instance end else --If inherited from common lua surface,Then set up the prototype and it will be called after the instance is constructed ctor Method -- inherited from Lua Object if super then cls = {} setmetatable(cls, {__index = super}) cls.super = super else cls = {ctor = function() end} end cls.__cname = classname cls.__ctype = 2 -- lua cls.__index = cls function cls.new(...) local instance = setmetatable({}, cls) instance.class = cls instance:ctor(...) return instance end end return cls end
Write a test code and note what went wrong
[plain] view plain copy
print?
local Base = class('Base') Base.__index = Base function Base:ctor(id) print('Base:ctor',id) self.id = id end local ExtBase = class('ExtBase',Base) ExtBase.__index = ExtBase function ExtBase:ctor(id) --Base:ctor(id) super(self,id) print('ExtBase:ctor',id) end
Influenced by traditional languages, the constructor of the base class is called on a subclass, which in fact results in the type itself being passed in directly as an object instance
Cause self to point to the type itself (also a table)
That's the only way to write it
[plain] view plain copy
print?
Base.ctor(self,id)
It's a bit ugly, so encapsulate the super function to look a little better.
Reproduction: http://blog.csdn.net/ring0hx/article/details/16341261
http://blog.csdn.net/myerel/article/details/38706225