메타테이블

*이 콘텐츠는 AI(베타)를 사용해 번역되었으며, 오류가 있을 수 있습니다. 이 페이지를 영어로 보려면 여기를 클릭하세요.

메타테이블은 테이블이 이전보다 더 강력해지도록 합니다. 메타테이블은 데이터에 부착되어 있으며 메타메서드라고 불리는 값을 포함합니다. 메타메서드는 특정 작업이 해당 데이터와 함께 사용될 때 호출됩니다.

메타테이블 조작하기

테이블의 메타테이블을 추가하고 찾기 위한 두 가지 주요 함수는 setmetatable()getmetatable()입니다.

local x = {}
local metaTable = {} -- 메타테이블도 테이블입니다!
setmetatable(x, metaTable) -- x에 metaTable이라는 메타테이블을 부여합니다!
print(getmetatable(x)) --> table: [16진수 메모리 주소]

setmetatable() 함수는 메타테이블을 설정하는 테이블도 반환하므로, 이 두 스크립트는 동일한 작업을 수행합니다:

local x = {}
setmetatable(x, {})
local x = setmetatable({}, {})

메타메서드

메타메서드는 메타테이블에 저장된 함수입니다. 이들은 테이블을 호출하거나, 테이블을 더하거나, 심지어 테이블을 나누는 등의 작업을 수행할 수 있습니다. 사용 가능한 메타메서드 목록은 다음과 같습니다:

메서드설명
__index(table, index)table[index]가 인덱싱될 때 호출되며, table[index]nil인 경우에만 작동합니다. 테이블로 설정할 수도 있으며, 이 경우 해당 테이블이 인덱싱됩니다.
__newindex(table, index, value)table[index]가 설정되려고 할 때 호출됩니다 (table[index] = value), table[index]nil인 경우에만 작동합니다. 테이블로 설정할 수도 있으며, 이 경우 해당 테이블이 인덱싱됩니다.
__call(table, ...)테이블이 함수처럼 호출될 때 호출됩니다. ...는 전달된 인수입니다.
__concat(table, value)테이블에 .. 연결 연산자가 사용될 때 호출됩니다.
__unm(table)단항 연산자가 테이블에 사용될 때 호출됩니다.
__add(table, value)+ 덧셈 연산자입니다.
__sub(table, value) 뺄셈 연산자입니다.
__mul(table, value)* 곱셈 연산자입니다.
__div(table, value)/ 나눗셈 연산자입니다.
__idiv(table, value)// 바닥 나눗셈 연산자입니다.
__mod(table, value)% 나머지 연산자입니다.
__pow(table, value)^ 거듭제곱 연산자입니다.
__tostring(table)테이블에서 tostring이 호출될 때 호출됩니다.
__metatable존재할 경우, 메타테이블을 잠궈 getmetatable()가 메타테이블 대신 이 값을 반환하고 setmetatable()가 오류를 발생시킵니다. 비함수 값입니다.
__eq(table, value)== 동등 연산자¹
__lt(table, value)< 미만 연산자¹
__le(table, value)<= 연산자¹
__mode약한 테이블에서 사용되며, 테이블의 키와/또는 값이 약한지 선언합니다. 약한 테이블 사용하기를 참조하세요.
__len(table)# 길이 연산자가 객체에 사용될 때 호출됩니다.
__iter(table)일반화된 반복을 사용할 때 사용자 정의 반복기를 나타내는 데 사용됩니다.

산술 또는 관계 메타메서드에 대한 함수를 작성할 때 두 개의 함수 매개변수는 메타메서드를 호출한 테이블과 다른 값 간에 서로 교환 가능합니다. 예를 들어, 벡터 연산을 수행할 때 스칼라 나눗셈은 교환 법칙이 성립하지 않습니다. 따라서 자신의 vector2 클래스에 대한 메타메서드를 작성할 때는 두 시나리오를 모두 고려해야 합니다.

local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a는 스칼라, b는 벡터입니다.
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a는 벡터, b는 스칼라입니다.
local vector, scalar = a, b
return vector2.new(vector.x / scalar, vector.y / scalar)
elseif (a.__type and a.__type == "vector2" and b.__type and b.__type == "vector2") then
-- a와 b 모두 벡터입니다.
return vector2.new(a.x / b.x, a.y / b.y)
end
end
function mt.__tostring(t)
return t.x .. ", " .. t.y
end
function vector2.new(x, y)
local self = setmetatable({}, mt)
self.x = x or 0
self.y = y or 0
return self
end
local a = vector2.new(10, 5)
local b = vector2.new(-3, 4)
print(a / b) -- -3.3333333333333, 1.25
print(b / a) -- -0.3, 0.8
print(2 / a) -- 0.2, 0.4
print(a / 2) -- 5, 2.5

사용법

메타테이블을 사용하는 방법은 여러 가지가 있습니다.

테이블에 단항 음수 연산자 적용하기

__unm 메타메서드는 단항 음수 연산자를 처리합니다. 이를 사용하여 테이블의 모든 값을 부정할 수 있습니다:

local metatable = {
__unm = function(t) -- __unm은 단항 - 연산자에 해당합니다.
local negated = {}
for key, value in t do
negated[key] = -value -- 이 테이블의 모든 값을 부정합니다.
end
return negated -- 테이블을 반환합니다.
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12

다른 테이블에서 누락된 값 인덱싱하기

다음은 __index를 사용하여 항목을 선언하는 흥미로운 방법입니다:

local metatable = {
__index = {x = 1}
}
local t = setmetatable({}, metatable)
print(t.x) --> 1

x가 테이블에서 인덱싱될 때 __index가 호출되었고, 찾을 수 없었습니다. Luau는 __index 테이블에서 x라는 인덱스를 찾기 위해 검색한 후, 하나를 발견하고 반환했습니다.

테이블을 함수처럼 호출하기

메타테이블은 누락된 값을 제공하는 것 이상을 할 수 있습니다. 다음 예를 보세요:

local t = {10, 20, 30}
print(t(5))

일반적으로 테이블을 호출할 수 없지만, 메타테이블을 사용하면 가능합니다:

local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- 인수(5)를 값에 더한 후, 새 테이블(t)에 배치합니다.
end
return unpack(sum) -- 개별 테이블 값을 반환합니다.
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35

테이블에 덧셈 연산자 적용하기

테이블을 더하는 것과 같은 더 많은 작업을 수행할 수 있습니다:

local table1 = {10, 11, 12}
local table2 = {13, 14, 15}
for k, v in table1 + table2 do
print(k, v)
end

이 코드는 테이블에서 산술을 수행하려고 시도하고 있다는 오류가 발생하지만, 메타테이블을 사용하면 작동합니다:

local metatable = {
__add = function(t1, t2)
local sum = {}
for key, value in t1 do
sum[key] = value
end
for key, value in t2 do
if sum[key] then
sum[key] += value
else
sum[key] = value
end
end
return sum
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
local table2 = setmetatable({13, 14, 15}, metatable)
for k, v in table1 + table2 do
print(k, v)
end

메타메서드를 호출하지 않고 값 설정하기

메타테이블을 사용할 때 몇 가지 문제가 발생할 수 있습니다. __index 메타메서드를 사용하여 테이블에 새 값을 생성해야 하지만, 해당 테이블의 메타테이블에도 __newindex 메타메서드가 있는 경우, 메타메서드를 호출하지 않고 값을 설정하기 위해 Luau 내장 함수 rawset()을 사용해야 합니다. 다음 코드는 이러한 함수들을 사용하지 않을 경우 발생하는 예시입니다.

local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- 테이블에 값을 일반적인 방법으로 설정하지 마세요
end
})
print(t[1]) -- 스택 오버플로우를 발생시킵니다

스택 오버플로우는 함수가 자신을 너무 많이 호출하려고 할 때 발생합니다. 위의 __index 함수에서 self[i]가 값으로 설정되므로, 다음 줄에 도달했을 때 self[i]는 존재해야 하며, 아마도 __index 메타메서드를 호출하지 않을 것입니다. 문제는 __newindex가 값을 설정하는 것을 허용하지 않는다는 것입니다. 그 존재는 표준 t[i] = v 방법으로 테이블에 값을 추가하는 것을 방해합니다. 이를 해결하기 위해 rawset() 함수를 사용하세요:

local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- 테이블에 값을 일반적인 방법으로 설정하지 마세요
end
})
print(t[1]) --> 10

약한 테이블 사용하기

테이블, 함수, 스레드 및 사용자 데이터를 보유하는 변수는 해당 객체의 복사본이 아닌 참조를 저장합니다. 객체를 다른 변수에 할당하면 동일한 객체에 대한 또 다른 참조가 생성됩니다:

local map = {key = 1}
local mapReference = map
print(map == mapReference) --> true
print(map == {key = 1}) --> false
local function func() end
local funcReference = func
print(func == funcReference) --> true
local part = Instance.new("Part")
local partReference = part
print(part == partReference) --> true

더 이상 강한 참조를 통해 도달할 수 없는 객체는 가비지 수집의 대상이 됩니다. 하나의 변수를 nil로 설정해도 다른 강한 참조가 여전히 해당 객체를 가리키고 있다면 객체는 가비지 수집의 대상이 되지 않습니다:

local child = {}
local parent = {Child = child}
child = nil
print(parent.Child) --> table: [16진수 메모리 주소]

child가 더 이상 자식 테이블을 참조하지 않지만, parent.Child는 여전히 참조하고 있습니다. 부모 테이블이 도달 가능하는 한, 그 참조는 자식 테이블을 도달 가능하게 유지합니다.

약한 테이블은 키, 값 또는 둘 다를 보유하되, 가비지 수집기가 그렇지 않은 객체를 회수하는 것을 방지하지 않습니다. 테이블의 메타테이블의 __mode 필드를 설정하여 어떤 참조가 약한지를 제어할 수 있습니다:

  • "k"는 키를 약하게 만듭니다.
  • "v"는 값을 약하게 만듭니다.
  • "kv" 또는 "vk"는 키와 값을 모두 약하게 만듭니다.

다음 코드 샘플은 약한 값을 가진 테이블을 생성합니다:

local children = setmetatable({}, {
__mode = "v",
})
do
local child = {}
children.example = child
end

do 블록이 끝난 후, children.example에 저장된 값은 강한 참조가 없으며 가비지 수집의 대상이 됩니다. 가비지 수집기가 자식 테이블을 회수하면, Luau는 children에서 해당 항목을 제거합니다. 가비지 수집은 비결정적이므로, 항목이 사라지는 시점에 의존하지 마세요.

제외한 모든 모드는 강한 상태로 유지됩니다. __mode = "v" 테이블의 키는 여전히 강한 참조를 유지하므로, 키로 저장된 객체는 여전히 도달 가능하며 테이블에 항목이 남아 있습니다.

Instances의 경우, 가비지 수집은 Luau 참조에 적용되며 객체 자체에는 적용되지 않습니다. 엔진은 인스턴스 데이터를 Luau VM 외부에 저장하므로, Luau 참조를 수집한다고 해서 인스턴스가 삭제되지는 않으며, 데이터 모델에 남아 있는 인스턴스는 Luau 참조를 유지하지 않습니다. 인스턴스를 해제하려면 Instance:Destroy()를 호출하고, 해당 인스턴스에 대한 모든 남은 참조를 지워야 합니다.

집합 데이터 유형 사용하기

집합은 순서가 없고 중복 요소가 없는 항목의 모음입니다. 항목은 집합에 있거나 없거나 합니다. 메타테이블을 사용하여 Luau 스크립트 내에서 집합을 구성하고 조작할 수 있습니다.

기본 메서드

다음 코드는 기본 집합 기능을 포함하여 새 집합을 구성하고, 항목을 추가 및 제거하고, 집합에 항목이 포함되어 있는지 확인하고, 집합의 내용을 출력할 수 있게 합니다.

local Set = {}
Set.__index = Set
-- 선택적 항목 목록에서 집합을 구성하는 함수
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- 집합에 항목을 추가하는 함수
function Set:add(item)
self[item] = true
end
-- 집합에서 항목을 제거하는 함수
function Set:remove(item)
self[item] = nil
end
-- 집합에 항목이 포함되어 있는지 확인하는 함수
function Set:contains(item)
return self[item] == true
end
-- 디버깅을 위해 집합을 쉼표로 구분된 목록으로 출력하는 함수
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
end

집합 생성

새 집합은 선택적 항목 배열을 추가하여 Set.new()를 호출하여 구성할 수 있습니다.

local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})

정의에 따라 집합은 순서 개념이 없습니다.

항목 추가

기존 집합에 항목을 추가하는 것은 Set:add() 메서드를 통해 수행할 수 있습니다.

local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:add("Mango")

항목 제거

집합에서 항목을 제거하려면 항목 이름과 함께 Set:remove()를 호출합니다.

local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:remove("Orange")

항목 확인

집합에 특정 항목이 포함되어 있는지 확인하려면 Set:contains()를 사용합니다.

local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
local result1 = fruits:contains("Cherry")
print(result1) -- true
local result2 = fruits:contains("Watermelon")
print(result2) -- false

추가 메서드

집합 간의 항목을 비교하거나, 집합을 결합하거나, 한 집합에서 다른 집합을 빼는 등의 유용한 작업을 구현할 수 있습니다.

교집합

집합을 벤 다이어그램으로 고려할 때, 두 집합의 교집합을 다음과 같이 얻을 수 있습니다. 즉, 두 집합 모두에 나타나는 항목입니다.

local function getIntersection(set1, set2)
local result = Set.new()
for key, value in set1 do
if set2:contains(key) then
result:add(key)
end
end
return result
end
local freshFruits = Set.new({"Mango", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
local frozenFruits = Set.new({"Mango", "Peach", "Pineapple"})
local commonFruits = getIntersection(freshFruits, frozenFruits)
commonFruits:output() -- Mango, Peach

합집합

다음 함수로 두 집합의 합집합을 얻을 수 있습니다. 즉, 중복 없이 두 집합의 항목 모음입니다. 이 함수는 메타테이블의 __add 메서드를 사용하여 set1 + set2의 덧셈 단축키를 제공합니다.

function Set:__add(otherSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in otherSet do
result[entry] = true
end
return result
end
local sweetFruits = Set.new({"Apple", "Mango", "Cherry", "Peach"})
local sourFruits = Set.new({"Lemon", "Lime"})
local allFruits = sweetFruits + sourFruits
allFruits:output() -- Peach, Lime, Apple, Cherry, Lemon, Mango

차집합

다음 함수를 통해 한 집합의 모든 항목을 다른 집합에서 제거할 수 있습니다. 위의 함수와 유사하게, 이 함수는 메타테이블의 __sub 메서드를 사용하여 set1 - set2의 뺄셈 단축키를 제공합니다.

function Set:__sub(otherSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in otherSet do
result[entry] = nil
end
return result
end
local allFruits = Set.new({"Apple", "Lemon", "Mango", "Cherry", "Lime", "Peach"})
local sourFruits = Set.new({"Lemon", "Lime"})
local sweetFruits = allFruits - sourFruits
sweetFruits:output() -- Mango, Apple, Cherry, Peach
©2026 Roblox Corporation. Roblox 및 Roblox 로고, 'Powering Imagination'은 미국 및 기타 국가 내 당사의 등록 및 미등록 상표입니다.