lua adds object-oriented

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

Posted by common on Sun, 30 Jun 2019 17:26:50 -0700