* 정의

 

__index는 테이블의 메타테이블 안에 정의할 수 있는 메타메소드이다. 메타테이블의 __index가 정의되어 있다면 테이블에서 정의되지 않은 키가 들어왔을 때, 바로 nil로 반환하지 않고 __index 메타메소드를 통해 반환하게 된다.

 

 

* 사용 이유

 

다음과 같은 기능을 활용하기 위해 사용된다.

 

  • 테이블에서 정의되지 않은 키가 들어왔을 때 메타테이블에 존재하는 __index를 통해 메타테이블을 조회할 수 있다.
  • 클래스의 상속을 구현할 수 있다.

 

 

* 동작 방식

 

- 테이블에 정의되지 않은 키를 추가로 찾아보는 과정

 

일반적으로 루아에서 테이블에 정의되지 않은 값에 접근 할 때 nil 값을 반환한다. 

 

이렇게 정의되지 않은 값에 접근할 때 루아 인터프리터는 해당 테이블의 메타테이블의 __index 메타메소드를 확인한다.

  • 만약 메타테이블이 존재하지 않거나 __index 메타메소드가 존재하지 않으면 nil을 반환한다.
  • 메타테이블이 존재하고 __index 메타메소드에 정의되어 있으면 __index를 통해 해당 값을 반환하게 된다.

 

 

- __index 함수 버전 예시

 

이렇게 명시적으로 테이블의 메타테이블 안에 __index 메타메소드를 정의하여 기존 테이블에 존재하지 않은 특정 값들에 대해 nil이 아닌 다른 값들을 반환하도록 설정할 수 있다. 다음과 같이 사용할 수 있다.

local myTable = setmetatable({key1 = "value1"}, {
	__index = function(myTable, key)

		if key == "key2" then
			return "metatablevalue"
		end
		
		-- 여기 들어온 이유가 myTable[key]가 nil이라서 들어온 것인데, 또 같은 조건이다. 무한 반복하게 되어 스택 오버플로가 발생한다.
		--return myTable[key]
		
		return nil
	end
})

print(myTable.key1, myTable.key2, myTable.key3)
 value1 metatablevalue nil

코드에서 보면 테이블에 key1밖에 존재하지 않지만 key2를 찾을 때 메타테이블을 선언할 때 정의한 내부 __index 메타메소드를 통해 nil이 아닌 "metatablevalue"을 반환하는 것을 볼 수 있다. 만약 __index 메타메소드가 정의되지 있지 않다면 nil을 반환했을 것이다. key3은 테이블과 __index를 통해서 찾을 수 없기 때문에 nil을 반환하게 된다.

 

 

- __index 단순화 버전 예시

 

__index를 함수가 아닌 테이블로 초기화할 수도 있다. 그러면 __index 테이블에서 키로 값을  찾게 된다.

메타테이블을 연결하고 __index 자체를 메타테이블로 지정하면 메타테이블을 그대로 상속받는 효과를 얻을 수 있다. 

local table1 = {
	value1 = 1,
	value2 = 2,
	value3 = 3,
	print = function() print("print function in table1") end,
	printValue = function(value) print("printValue function in table1 => ".. tostring(value)) end
}

-- 메타테이블로 사용할 때 해당 테이블에 특정 키가 없으면 이 테이블에서 찾아보겠다는 뜻이다.
table1.__index = table1

local table2 = setmetatable({value4 = 4, value5 = 5}, table1)

print(table2.value1)
table2.print()
table2.printValue(3)
1
print function in table1
printValue function in table1 => 3

 

위의 코드를 함수 버전으로 구현하면 다음과 같다.

local table1 = {
	value1 = 1,
	value2 = 2,
	value3 = 3,
	print = function() print("print function in table1") end,
	printValue = function(value) print("printValue function in table1 => ".. tostring(value)) end
}

table1.__index = function(table, key) 
	local metatable = getmetatable(table)
	
	--if not metatable then
	--	return nil
	--end
	
	return metatable[key]
end

local table2 = setmetatable({value4 = 4, value5 = 5}, table1)

print(table2.value1)
table2.print()
table2.printValue(3)
1
print function in table1
printValue function in table1 => 3

 

 

- __index 함수 버전과 단순화 버전 호출 원리(추측)

 

두 버전을 적절하게 지원하는 이유는 myTable.key / myTable[key]로 접근하였을 때 특정 함수를 호출하여 적절하게 처리한다고 생각한다. 함수는 다음과 같이 구현되어 있을 가능성이 높다.(자료 서칭과 디버깅을 통해 작성된 코드입니다. 정확하지 않을 수 있습니다.)

local function GetValueByKey(table, key)

	-- table[key]을 사용하면 다시 메타테이블의 __index를 통해서 찾기 때문에 실제로 원하는대로 동작하지는 않는다. 
	-- if table[key] then return table[key] end
	
	
	-- 따라서 메타메소드를 우회할 수 있는 rawget함수를 사용한다.
	local value = rawget(table, key)
	if value then return table[key] end

	-- 메타테이블에서 추가로 찾아본다.	
	local metatable = getmetatable(table)
	if not metatable or not metatable.__index then return nil end

	-- 타입에 따라서 적절하게 반환해준다.
	local typeString = type(metatable.__index)

	if typeString == "table" then

		return metatable.__index[key]

	end

	-- __index값으로 table 이외에는 무조건 function을 넣어야 크래시가 발생하지 않는다.
	return metatable.__index(table, key)
end

여기서 주의할 점은 table[key] 연산 자체가 내부적으로 메타메소드를 호출하고 있고, table[key] 무한 루프에 빠질 수 있기 때문에 메타메소드를 호출하지 않고 우회하는 함수인 rawget을 사용하여 해당 테이블에 범위에서만 키가 존재하는 지 검사 해야한다.

 

 

- __index 정의 시 주의 사항

 

  1. 여기서 주의해야할 점은 __index 내부로 들어오는 매개변수는 해당 테이블과 키값으로 들어온다는 점이다.
  2. __index 메타 메소드 내부에서는 다시 해당 테이블을 키값으로 접근하게 되면 재귀가 무한 반복되니 주의하여야 한다.
  3. 2번과 같은 논리가 테이블에도 적용된다. metatable.__index = table 으로 설정하면 문제가 생긴다.

__index 메타메소드가 호출된 이유는 myTable[key]가 nil이라서 들어온 것인데, __index 메타 메소드에서 myTable[key]를 접근하면 또 nil이 나오고 다시 __index 메타메소드를 호출하게 된다. 결국 무한 반복하게 되어 스택 오버플로가 발생한다. 

 

* 정의

 

__newindex는 테이블의 메타테이블 안에 정의할 수 있는 메타메소드이다. 테이블에서 테이블에 정의되어 있지 않은 새로운 키를 할당하려고 할 때 해당 테이블의 메타테이블에서 __newindex가 정의되어 있다면 새로운 키에 대한 처리 방식을 정의할 수 있다. 

 

 

* 사용 이유

 

새로운 키가 할당되려고 할 때 동작방식을 정의함으로 써 여러 가지 기능을 만들어낼 수 있다. 예를 들어보면 읽기 전용 테이블을 구현할 수도 있다.

 

 

* 동작 방식

 

- 테이블에 새로운 키를 처리하는 과정

 

만약 테이블에 존재하지 않는 키에 대해 값을 할당하려고 하면  루아 인터프리터는 __newindex 메타메소드를 찾아보고 만약 __newindex가 정의되어 있다면 값을 할당하는 대신에 __newindex 메타메소드를 호출한다. 

 

__newindex는 __index와 함께 사용하면 활용도가 더 높다. 읽기 전용 테이블, 기본 값을 가지고 있는 테이블, 상속 관련 기능들도 구현할 수 있다. 

 

(메타메소드를 호출하지 않고 우회할 수 있는 rawset이라는 원시 함수를 제공한다. 이는 내부 로직을 구현할 때 무한 루프 방지 등 유용하게 사용될 수 있다.)

 

 

- __newindex 함수 버전 예시

 

__index와 다르게 새로운 값들을 할당하는 과정이기 때문에 value 매개변수도 같이 들어온다. 예시에서는 메타테이블에 정보를 저장하도록 만들었다.

local table1 = {}
local table2 = setmetatable({value1 = 1}, table1)
table1.__newindex = function(table, key, value)
	
	local metatable = getmetatable(table)
	
	--if not metatable  then
	--	-- 원래 테이블에 추가하도록 할수도 있다.
	--	-- rawset(table, key, value) -- 무한 재귀를 막기 위해 메타메소드 우회
	--	return
	--end
	
	-- metatable도 __newindex가 정의되어 있다면 이에 따라 수행된다.
	-- 다 찾아보고 부모 클래스도 없으면 그냥 본인 테이블에 넣는 것으로 정했다.
    -- 제일 아래에 있는 부모 클래스에 넣을 수도 있다.
	local rv = metatable[key]
	if not rv then
		rawset(table, key, value)
	else
		metatable[key] = value
	end
end

table2.newValue = 5
print(table2.newValue, table1.newValue)

table2.value1 = 10
print(table2.value1, table1.value1)
nil 5
10 nil

 

 

- __newindex 단순화 버전 예시

 

간단히 함수가 아닌 테이블로 지정하면 해당 테이블로 할당하게 된다.

local table1 = {}
local table2 = setmetatable({value1 = 1}, table1)
table1.__newindex = table1

table2.newValue = 5
print(table2.newValue, table1.newValue)

table2.value1 = 10
print(table2.value1, table1.value1)
nil 5
10 nil

 

 

- __newindex 함수 버전과 단순화 버전 호출 원리(추측)

 

myTable[key] = value / myTable.key = value로 할당할 때 다음과 같은 함수가 호출되어 처리될 것이라고 추측하고 있다. 메타메소드를 우회하기 위해 rawget, rawset을 사용하였다.

local function SetKeyValue(table, key, value)
	
	-- 원래 있는 값이면 그냥 갱신해준다. 메타메소드를 우회하기 위해 rawget을 사용한다.
	local targetValue = rawget(table, key)
	if targetValue then
		rawset(table, key, value)
		return
	end
	
	-- 메타테이블에서 __newindex가 정의되어 있지 않다면 테이블에 그냥 넣어준다. 기본 동작이다.
	local metatable = getmetatable(table)
	if not metatable or not metatable.__newindex then 
		-- table[key] = value를 사용하면 다시 메타테이블의 __newindex 통하게 되기 때문에 무한 재귀가 발생한다.
		-- table[key] = value

		-- 따라서 메타메소드를 우회할 수 있는 rawset함수를 사용한다.
		rawset(table, key, value)
		return
	end

	-- 타입에 따라서 적절하게 할당해 준다.
	local typeString = type(metatable.__newindex)

	if typeString == "table" then

		metatable.__newindex[key] = value
		return
	end

	metatable.__newindex(table, key, value)
end

 

 

 

* 정의

 

메타테이블(metatable)은 키 세트(key set)와 관련 메타 메소드의 도움으로 메타테이블과 연결된 테이블 동작에 대한 수정을 도와주는 테이블이다.

 

 

* 사용 이유

 

다음과 같은 기능을 활용하기 위해 사용된다.

 

  • 테이블 연산자(operator)에 기능을 변경 및 추가할 수 있다.
  • 테이블에서 정의되지 않은 키가 들어왔을 때 메타테이블에 존재하는 __index를 통해 메타테이블을 조회할 수 있다.
  • 클래스의 상속을 구현할 수 있다.

 

* 동작 방식

 

- setmatatable, getmatatable

 

메타테이블을 사용하기 위한 두 가지 중요한 메소드를 제공한다.

 

  • setmetatable(table, metatable) : 해당 테이블의 메타테이블을 설정하기 위해 사용된다.
  • getmetatable(table) - 해당 테이블의 메타테이블을 가져오기 위해 사용된다.

 

setmetatable은 다음과 같이 사용할 수 있다.

-- mytable = setmetatable({},{})
mytable = {}
mymetatable = {}
setmetatable(mytable,mymetatable)

 

 

- __index

 

https://create-new-worlds.tistory.com/363

 

[Lua] __index

* 정의 __index는 테이블의 메타테이블 안에 정의할 수 있는 메타메소드이다. 만약 테이블에서 정의되지 않은 값을 바로 nil로 반환하지 않고 추가로 __index 메타메소드를 확인하여 적절한 값을 반

create-new-worlds.tistory.com

 

 

- __newindex

 

https://create-new-worlds.tistory.com/369

 

[Lua] __newindex

* 정의 __newindex는 테이블의 메타테이블 안에 정의할 수 있는 메타메소드이다. 테이블에서 테이블에 정의되어 있지 않은 새로운 키를 할당하려고 할 때 해당 테이블의 메타테이블에서 __newindex가

create-new-worlds.tistory.com

 

 

- operator 메타메소드, 기타 메타메소드

 

+, -, *, /, % 등 수 많은 연산자에 대해서도 행동을 정의할 수 있다. 연산자 오버로딩과 비슷하게 생각하면 된다.

 

그 외에도 메소드의 동작을 정의할 수 있는 __call 메타메소드, print 에서의 동작 방식을 변경할 수 있는 __tostring등이 존재한다.

 

자세한 내용은 여기서 볼 수 있다.

https://www.tutorialspoint.com/lua/lua_metatables.htm#

 

Lua - Metatables

Lua - Metatables A metatable is a table that helps in modifying the behavior of a table it is attached to with the help of a key set and related meta methods. These meta methods are powerful Lua functionality that enables features like − Changing/adding

www.tutorialspoint.com

 

+ Recent posts