C++: Golang-ish `defer`-macro
별거 없는데, 문득 심심해서 툭닥거려봤다.
원하는 것과 접근법
- https://go.dev/blog/defer-panic-and-recover
- https://docs.rs/defer/latest/defer/fn.defer.html
- https://ziglang.org/documentation/master/#defer
golang/zig에서처럼 defer
등록한 LIFO순서대로 호출되도록.
검색해보면, 대부분 RAII을 사용하거나 하는 것들이 보인다:
- DDG: c++ golang defer
- https://stackoverflow.com/questions/33050620/golang-style-defer-in-c
- https://stackoverflow.com/questions/45617758/proper-way-to-release-resources-with-defer-in-a-loop
- https://codesire-deng.github.io/2022/02/06/One-Minute-to-C-defer/
…그냥 std::shared_ptr<void>
을 이용하는 방식이 마음에
들었다. 그리고 조금 더 간단하게 사용하려고 매크로로 만들어 봤다.
defer.inl
:
/* -*- mode: c++; -*- */
#ifndef defer_INL
#define defer_INL 1
#include <memory>
#define _Defer_CONCAT_IMPL( x, y ) x##y
#define _Defer_CONCAT( x, y ) _Defer_CONCAT_IMPL( x, y )
#define Defer(blk) std::shared_ptr<void> _Defer_CONCAT(Defer_, __COUNTER__ )(nullptr, blk)
#endif /*defer_INL*/
__COUNTER__
이용해서 매크로가 생성한 std::shared_ptr<..>
변수의
이름이 겹치지 않도록 해봤다. (…위에 검색한 링크들에도 사용하듯이)
사용예
defer.cpp
정도로 저장하고, chmod +x defer.cpp && ./defer.cpp
:
#if 0
g++ -v -pedantic -Wall -Wextra -Wpedantic -Wconversion -std=c++11 -O0 -g -fsanitize=address "$0" -o "$0".exe && ./"$0".exe ; exit
#endif
#include <cstdio>
#include <cstdlib>
#include "defer.inl"
int main(void)
{
int* x = new int{42};
Defer([&](...){
printf("defer: main #1: %d\n", *x);
delete x;
});
Defer([](...){
printf("defer: main #2\n");
});
return EXIT_SUCCESS;
}
- asan 사용해서 간단하게 잘 동작하는지 확인해봤다.
- TIP: shell script처럼 그냥 cpp 소스파일 실행하면, 스스로 컴파일하고, 스스로 실행하도록 해봤다. 컴파일옵션 변경해서 써도 좋고, 아니면 직접 커맨드라인에서 옵션들 변경해서 컴파일해도 되겠지.
제약사항과 생각해볼꺼리
- try/catch/finally 대신, 이렇게 처리하는 것도 간결하기는 한거 같아.
- C++은 원래 RAII이니까, 다른 try/catch/finally 같은 접근보다는 이게 더 'C++ idiomatic'하겠지 싶어,,라고 자기만족.1
- 그리고 당연히, setjmp/longjmp등과 섞어 쓰면 동작하지 못한다. ㅋㅋ 2