assert & static_assert
Intro
기존의 C++에 존재하는 #error 전처리기 지시자와 assert 매크로를 통하여 assertion 처리가 가능하다.
프로그램이 수행되는 동안 반드시 유지되어야 할 불변식(invariant)을 assert를 이용해서 검증 가능하다.
예를 들어, 동적으로 배열을 할당할 때 배열 사이즈는 반드시 양수여야 하는데,
이를 assert를 써서 검증할 수 있다.
1.1 Assert
C : #include <assert.h>
C++ : #include <cassert>
#include <iostream>
#include <cassert>
using namespace std;
void foo (int age)
{
// assert를 이용한 유효성 확인
assert(age > 50);
int* p = new int[age];
}
int main(int argc, char* argv[])
{
foo(26);
return 0;
}
위와 같이 assert를 이용한다면 오류가 발생하면 개발자에게 프로그램이 다양한 정보(Call Stack, 발생 위치 등)를 알려주어 유효성을 쉽게 검증 할 수 있도록 도와준다.
하지만 assert는 매크로이기 때문에 사용에 주의해야한다.
함수 원형
#define assert(expression) (void)( \
(!!(expression)) || \
(_wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0)\
)
사용 예시
assert(std::is_same_v<int, int>); // error: assert does not take two arguments
assert((std::is_same_v<int, int>)); // OK: one argument
static_assert(std::is_same_v<int, int>); // OK: not a macro
std::complex<double> c;
assert(c == std::complex<double>{0, 0}); // error
assert((c == std::complex<double>{0, 0})); // OK
1.2 #error
#errortoken-string
#error는 위와같은 형태로 되어있다.
#if !defined(__cplusplus)
#error C++ compiler required.
#endif
전처리기가 전처리기 지시자인 #error를 만나게되면 컴파일 오류를 발생시키며 뒤에 나오는 token-string 문자열을 화면에 출력한다. 즉, error가 발생할것 같이 컴파일 시점에 강제로 error를 발생시킨다.
하지만 이러한 assert와 #error에는 중요한 단점이있다. assert의 경우 런타임에 비교를 수행하기 때문에 성능의 하락을 가져올 수 있다. #error의 경우 컴파일 시간에 처리가 가능하나 템플릿이 구체화 되기 전에 수행되므로 C++에서는 적절하지 못하다.
이러한 문제점을 해결하기 위해 탄생한 문법이 바로 static_assert이다.
2. static_assert
C++11에 추가된 static_assert는 컴파일 타임에 소프트웨어 assertion을 위해서 추가된 문법이다. 만약 작성된 상수 표현식(constant-expression)이 false(거짓)일 경우에 컴파일러는 메시지를 출력하고 C2338 에러를 출력한다. 반대로 true(참)인 경우에는 아무 영향을 주지 않는다.
static_assert( constant-expression, string-literal );
static_assert( constant-expression ); // Visual Studio 2017 and later
앞의 constant-expression 부분은 bool값으로 return 되어야 한다. 이 부분이 false 일 때, 컴파일러가 string-literal를 표시하게 된다.
struct Command {
char cmd;
int len;
};
template <class T>
void foo(T& a) {
static_assert(sizeof(Command) == sizeof(a.cmd) + sizeof(a.len), "Command size is different");
}
int main() {
Command a;
foo(a);
}
위와 같은 상황에서 Command 클래스의 사이즈에 padding이 들어갔는지 확인하는 예제이다. 기본적으로 padding이 들어 Command의 사이즈는 8이 출력되는상황에서 char와int의 사이즈를 더한 5와 다르므로 Command size is different라는 출력문을 볼 수 있게 된다.
#include <type_traits>
struct Myclass {
Myclass(const Myclass&) = delete;
Myclass() = default;
};
template <class T>
void my_assert(T& a)
{
static_assert(std::is_copy_constructible<T>::value, "Copy constructure is unavailable");
}
int main()
{
Myclass a;
my_assert(a);
}
또한 위처럼 템플릿을 사용하는 상확에서 복사 생성자를 지운 경우 사용 가능성을 검증하는 코드에서 검증을 할 수 있다.
이처럼 static_assert는 컴파일 시간에서의 검증을 지원하기 때문에 성능을 높일수가 있으며 템플릿을 지원하기 때문에 템플릿의 에러도 잡아낼 수 있다.