C++ static 람다가 예상과 다르게 동작하는 이유
최근 C++ 코드를 정리하다가 발견하기 꽤 어려운 버그를 하나 만났다. 컴파일도 잘 되고, 런타임 에러도 없는데 결과만 이상하게 나오는 경우였다.
문제의 코드는 대략 이런 형태였다.
RzVoid RzBuffManager::DetachPropBuff( const RzBool bOnlyPropBuff, RzKeySet& outCooltimeKeySet, RzKeySet& outBuffKeySet )
{
static const RzBuffDeleteHandler handler = [bOnlyPropBuff]( const RzBuffDeleteParamInfo& paramInfo, const RzBuffPtr& spBuff )
{
if ( bOnlyPropBuff )
{
// blah blah...
}
return true;
};
doAnything();
}
겉으로 보기에는 큰 문제가 없어 보인다. Visual Studio 2019에서도 컴파일이 잘 되고, 실행 중에도 별다른 오류가 발생하지 않는다. 하지만 실제 동작은 기대와 달랐다.
핵심은 handler가 static이라는 점이다. 이 람다는 지역 정적 변수로 한 번만 초기화되며, 그 시점에 캡처한 bOnlyPropBuff 값도 함께 고정된다.
즉, 처음 함수가 호출될 때 bOnlyPropBuff가 true였다면 이후 호출에서 false를 넘겨도 람다 내부에서는 계속 처음 값만 사용하게 된다. 반대로 첫 호출이 false였다면 그 뒤로도 계속 false처럼 동작한다.
나는 매 호출마다 람다가 현재의 bOnlyPropBuff 값을 받아서 동작하길 기대했다. 하지만 static으로 선언된 순간, 이 람다는 “매번 새로 만들어지는 함수 객체”가 아니라 “처음 한 번 만들어진 뒤 계속 재사용되는 객체”가 되어버린다.
이런 종류의 버그가 까다로운 이유는 다음과 같다.
- 컴파일 에러가 없다.
- 런타임 크래시도 없다.
- 코드만 얼핏 보면 캡처한 값이 매번 바뀔 것처럼 보인다.
결국 해결 방법은 단순하다. 호출마다 다른 값을 반영해야 하는 람다라면 static을 제거해야 한다. 반대로 static을 유지하고 싶다면, 호출마다 변하는 값을 캡처에 의존하지 않도록 구조를 바꿔야 한다.
작은 차이처럼 보여도, static 지역 변수와 람다 캡처가 만나면 꽤 교묘한 버그가 만들어질 수 있다. 비슷한 패턴을 사용하고 있다면 한 번쯤 다시 점검해 보는 것이 좋겠다.