Post

(C/C++) define, inline, const, enum, enum class

#define의 쓰임

#define옵션이나 flag를 지정할 때만 사용 하고, 다른 용도일 경우 inline이나 static const를 사용하는 편이 좋다. 이렇게 할 경우 1. type-safe하고, 2. 어디에 쓰느냐에 따라 scope도 지정 가능하며, 3. 디버깅 심볼이 생성되어 디버깅이 편하다는 장점이 있다.

간단한 함수 차원의 #define / inline

C에서 사칙연산 같은 간단한 작업은 #define macro를 활용해 왔으나, 요즘은 C에서도 inline이 가능하므로 이 쪽을 권장한다.

1
extern inline void f() { }

C++에서도 #define은 꼭 필요한거 아니면 쓰지 않는 것을 권장한다. (Google C++ Style Guide) 단순 치환으로 call 없이 in-line 되기 때문에 calling overhead를 줄일 수 있고 좀 더 간결해진다. 또한 컴파일 타임(정확히는 전처리)에 단순 치환 된다는 점을 고려해보면 여러가지로 활용이 가능하다. C++에서는 inline 키워드를 붙이면 in-line 함수를 만들 수 있다. C++에서 .h와 .cpp를 분리하는 경우라면, 인라인 함수의 바디는 반드시 .h에 들어가야 한다. 이는 컴파일러에게 이를 인라이닝하라는 힌트를 주는 것이나 다름 없기 때문에, inline 키워드를 붙이지 않아도 대체로 인라이닝된다. inline은 함수 구현부가 함수 호출 부분에 그대로 치환되어 별다른 call 없이 함수의 기능을 수행하는 것을 의미한다. 따라서 calling overhead를 줄일 수 있다.

상수 차원의 #define / static const / enum

#define은 지양하고, constenum 중 골라쓰면 되는데, 두 개의 용도가 다르기 때문에 상황에 맞게 쓰면 된다. 나는 보통 enum을 쓰고 문자열같이 enum을 사용할 수 없는 경우 const char\*를 사용한다. const는 함수 내부에서 사용하고 상수(e.g., BUF_SIZE)를 정의할 때는 enum [class]을 사용하는 편이 좋겠다. 다만 다음 처럼 static변수의 크기를 결정할 때는 c #define을 사용하거나, 직접 입력해야 한다.

1
2
3
4
5
6
7
8
9
10
11
#define PATH\_LEN\_D 15
static const int PATH\_LEN\_C = 15;
enum : int {
BUF\_SIZE = 4096
}
char\* parse\_something(const char\* dev) {
static \_\_thread char ret\_str[PATH\_LEN\_C];    // error!
snprintf(ret\_str, PATH\_LEN\_D, "/aaa/%s/ccc", dev);
return ret\_str;
}
>>> error: storage size of ret\_str isnt constant

예제

1
2
3
4
5
6
7
8
enum NI : int {  // or enum class
MAXHOST     = 1025,
NUMERICHOST = 1
};
namespace Path {
const char\* SYS\_NET  = "/sys/class/net/%s/address";
const char\* PROC\_ARP = "/proc/net/arp";
};

enum VS enum class

enumenum class는 다르다. 전자는 C언어에서 사용하는 방식이고, 후자는 C++11 이후 그리고 대부분의 언어에서 사용하는 방식이다. 전자의 경우 스코프 문제가 있다. enum 이름을 명시하지 않아도 접근 가능하다는 점인데, 즉 전역 네임스페이스를 사용하기 때문에 C++에서는 namespace로 묶어주는 방식을 사용해야만 한다.

1
2
3
4
5
6
7
8
9
namespace Len {
enum T : int {

IP\_STR\_BUF      = 16
};
}
/\*\* external source \*\*/
sysinfo::IP\_STR\_BUF    // OK.
sysinfo::Len::IP\_STR\_BUF    // OK.

그러나 후자는 클래스처럼 이름을 명시해야 사용 가능하며 따라서 네임스페이스를 추가로 사용할 필요가 없다. 이 밖에도 type safety, 선언 정의 분리 등의 장점이 있다.

전반적으로 타 언어와 통일성이나 여러 장점 때문에 enum class를 사용하는 편이 더 좋기는 한데, 이건 배열의 크기로 들어갈 때 자동으로 캐스팅이 안되서 다음과 같이 해줘야 하는 번거로움이 있다. 그래서 배열 크기로 지정할 상수가 필요할 때는 그냥 enum을 사용하는게 편리한 것 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
/\*\* enum class \*\*/
enum class Len {
IP\_STR\_BUF = 16
};
char src\_ip\_str[static\_cast<int>(Len::IP\_STR\_BUF)];

  

/\*\* enum \*\*/
enum Len : int {
IP\_STR\_BUF = 16
};
char src\_ip\_str[Len::IP\_STR\_BUF];

결론적으로 어떤 클래스에 속하지 않고 외부에 위치하는 enum은 다음과 같이 enum 이름을 적지 않으면서 네임 스페이스로 감싸주는 방법을 사용하면, static casting을 하지 않아도 되고 namespace 충돌도 피할 수 있다. 단, enum을 파라미터로 받을 때 네임스페이스 이름만 명시할 수 없으니, Len::T param 같은 식으로 받을 수 있게 T를 적어준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace sysinfo {
....
namespace Len {
enum T : int {
IP\_STR\_BUF      = 16,
MAC\_STR\_BUF     = 18,
SYS\_NET\_PATH    = 24,
INT\_NAME\_MAX    = 15,
ARP\_TABLE\_ENTRY = 128
};
}
....
}

어떤 클래스에 속하는 enum의 경우, 클래스 내부에서 namespace를 지정할 수 없고, 그렇게 할 필요도 없는게 어차피 클래스 스코프로 감싸지기 때문이다. 그냥 enum 이름 지정해서 사용하면 된다.

This post is licensed under CC BY 4.0 by the author.