C++: Golang-ish `defer`-macro

Posted on Mar 2, 2023

별거 없는데, 문득 심심해서 툭닥거려봤다.

원하는 것과 접근법

  1. https://go.dev/blog/defer-panic-and-recover
  2. https://docs.rs/defer/latest/defer/fn.defer.html
  3. https://ziglang.org/documentation/master/#defer

golang/zig에서처럼 defer 등록한 LIFO순서대로 호출되도록.

검색해보면, 대부분 RAII을 사용하거나 하는 것들이 보인다:

…그냥 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;
  }
  1. asan 사용해서 간단하게 잘 동작하는지 확인해봤다.
  2. TIP: shell script처럼 그냥 cpp 소스파일 실행하면, 스스로 컴파일하고, 스스로 실행하도록 해봤다. 컴파일옵션 변경해서 써도 좋고, 아니면 직접 커맨드라인에서 옵션들 변경해서 컴파일해도 되겠지.

제약사항과 생각해볼꺼리

  1. try/catch/finally 대신, 이렇게 처리하는 것도 간결하기는 한거 같아.
  2. C++은 원래 RAII이니까, 다른 try/catch/finally 같은 접근보다는 이게 더 'C++ idiomatic'하겠지 싶어,,라고 자기만족.1
  3. 그리고 당연히, setjmp/longjmp등과 섞어 쓰면 동작하지 못한다. ㅋㅋ 2

Footnotes


1

C++을 Java처럼 쓸 이유도 없고, C++은 그자체로 다른 특성과 장단점이 있으니까.. 그걸 꼭 자바처럼 되어야 한다고 할 필요는 없겠지.

2

신비로운 SEH/libunwind등으로 어떻게 해킹해서 되게 해도 되겠지만… 생각해보면 어차피 C++ exception handling이 자연스럽게 destructor호출해주고 하니까.