정적 const 대 #define


212

전 처리기 static const보다 vars 를 사용하는 것이 더 낫 #define습니까? 아니면 상황에 따라 달라질 수 있습니까?

각 방법의 장점 / 단점은 무엇입니까?


14
Scott Meyers는이 주제를 매우 훌륭하고 철저하게 다룹니다. "Effective C ++ Third Edition"에서 그의 아이템 # 2. 두 가지 특수한 경우 (1) 클래스 특정 상수에 대해 클래스 범위 내에서 정적 const가 선호됩니다. (2) 네임 스페이스 또는 익명 범위 const가 #define보다 선호됩니다.
Eric

2
나는 열거 형을 선호합니다. 그것은 둘의 하이브리드이기 때문에. 변수를 만들지 않으면 공간을 차지하지 않습니다. 상수로 사용하려면 enum이 가장 좋습니다. C / C ++ 11 표준에서 형식 안전성과 완벽한 상수를 가지고 있습니다. #define은 unsafe 유형이며 const가 컴파일러가 최적화 할 수 없으면 공간을 차지합니다.
siddhusingh

1
내 의사 결정의 사용 여부 #definestatic const에 의해 구동된다 (문자열) 초기화 (이것은 아래의 답변을 통해 언급되지 않았다) 측면 : 상수는 특정 컴파일 단위 내에서 사용되는 경우에만, 그때로 이동 static const, 다른 I 사용 #define- 피하기 정적 순서 초기화 실패 isocpp.org/wiki/faq/ctors#static-init-order
Martin Dvorak

경우 const, constexpr또는 enum또는 경우에 어떤 변화의 작품, 다음에 선호#define
Phil1970

@MartinDvorak " 정적 순서 초기화를 피 하라" 상수에 문제가있는 방법은 무엇입니까?
curiousguy

답변:


139

개인적으로, 나는 전처리기를 혐오하므로 항상 함께 갈 것입니다 const.

a의 주요 장점 #define은 실제로 텍스트를 리터럴 값으로 대체하기 때문에 프로그램에 저장할 메모리가 필요 없다는 것입니다. 또한 유형이 없다는 장점이 있으므로 경고를 생성하지 않고 모든 정수 값에 사용할 수 있습니다.

" const"의 장점은 범위를 지정할 수 있으며 개체에 대한 포인터를 전달해야하는 상황에서 사용할 수 있다는 것입니다.

static그래도 " "부분으로 무엇을 받고 있는지 정확히 모르겠습니다 . 전역 적으로 선언하는 경우을 사용하는 대신 익명 네임 스페이스에 넣습니다 static. 예를 들어

namespace {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}

8
문자열 상수는 특히 #define더 큰 문자열 상수를위한 "빌딩 블록"으로 사용될 수있는 경우 d 가되는 것이 유리할 수있는 것 중 하나입니다 . 예를 들어 내 답변을 참조하십시오.
AnT

62
#define메모리를 사용하지 않는 장점이 정확하지 않습니다. 예에서 "60"은 static const또는에 관계없이 어딘가에 저장해야합니다 #define. 실제로 #define을 사용하면 대량의 (읽기 전용) 메모리 소비가 발생하고 정적 const는 불필요한 메모리를 사용하지 않는 컴파일러를 보았습니다.
Gilad Naor

3
#define은 입력 한 것과 같으므로 메모리에서 확실히 나오지 않습니다.
목사

27
@theReverend 리터럴 값은 어떻게 든 기계 리소스 소비에서 면제됩니까? 아니요, 그들은 다른 방식으로 사용할 수도 있습니다. 아마도 스택이나 힙에 나타나지 않을 수도 있지만 어느 시점에서 프로그램은 컴파일 된 모든 값과 함께 메모리에로드됩니다.
Sqeaky

13
@ gilad-naor, 당신은 일반적으로 맞지 만 60과 같은 작은 정수는 실제로 부분적인 예외 일 수 있습니다. 일부 명령어 세트는 명령어 스트림에서 직접 정수 또는 정수 서브 세트를 인코딩 할 수 있습니다. 예를 들어 MIP는 즉시 추가합니다 ( cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/addi.html ). 이런 종류의 경우 #defined 정수는 컴파일 된 바이너리에서 어쨌든 존재 해야하는 명령어에서 몇 개의 여분 비트를 차지하기 때문에 공간을 사용하지 않는다고 말할 수 있습니다.
ahcox 2016 년

242

사용법에 따라 s와 #defines 사이에 장단점 :constenum

  1. enum에스:

    • 정수 값에만 가능
    • 적절하게 범위가 지정된 / 식별자 충돌 문제는 특히 열거에 enum class X의해 열거 가 명확하지 않은 C ++ 11 열거 형 클래스에서 잘 처리 됩니다.X::
    • 강력하게 입력되었지만 C ++ 03에서 제어 할 수없는 충분히 큰 부호있는 부호있는 int 크기 (열거 형이 struct / C ++ 11은 기본적으로 int프로그래머가 명시 적으로 설정할 수 있지만 class / union)
    • 주소를 취할 수 없습니다-열거 값이 사용 지점에서 인라인으로 효과적으로 대체되므로 하나도 없습니다
    • 강력한 사용 제한 (예 : 증분- template <typename T> void f(T t) { cout << ++t; }컴파일하지는 않지만 암시 적 생성자, 캐스팅 연산자 및 사용자 정의 연산자를 사용하여 열거 형을 클래스로 래핑 할 수 있음)
    • 각각의 상수 유형은 둘러싸는 열거 형에서 template <typename T> void f(T)가져 오므로 다른 열거 형에서 동일한 숫자 값을 전달할 때 고유 한 인스턴스화를 얻습니다. 이는 모두 실제 f(int)인스턴스화와 다릅니다 . 각 함수의 객체 코드는 동일 할 수 있지만 (주소 오프셋 무시) 컴파일러 / 링커가 불필요한 사본을 제거하지는 않지만 걱정하는 경우 컴파일러 / 링커를 확인할 수는 없습니다.
    • 도에 대해서 typeof / decltype, 참으로, "법"의 조합도 소스 코드에 표기되지 않은 의미있는 값과 조합 (세트에 유용한 통찰력을 제공 고려 numeric_limits을 기대할 수없는 것은 enum { A = 1, B = 2 }- 인 A|B프로그램 로직에서 "법적" 원근법?)
    • 열거 형의 유형 이름은 RTTI, 컴파일러 메시지 등의 여러 위치에 나타날 수 있습니다. 유용하고 난독화할 수 있습니다.
    • 변환 단위가 실제로 값을 보지 않으면 열거를 사용할 수 없으므로 라이브러리 API의 열거에는 헤더에 노출 된 값이 필요 make하며 다른 타임 스탬프 기반 재 컴파일 도구는 클라이언트 재 컴파일이 변경되면 트리거됩니다 (나쁜! )

  1. const에스:

    • 적절한 범위 / 식별자 충돌 문제를 잘 처리
    • 강력하고 단일 한 사용자 지정 유형
      • 당신은 #defineala 을 "type"하려고 시도 할 수 #define S std::string("abc")있지만, 상수는 각 사용 지점에서 별개의 임시의 반복적 인 구성을 피합니다
    • 하나의 정의 규칙 합병증
    • 주소를 취하고 그들에 대한 const 참조를 만들 수 있습니다.
    • const가치 와 가장 유사 하여 둘 사이를 전환 할 때 작업과 영향을 최소화합니다
    • 구현 파일 내에 값을 배치하여 현지화 된 재 컴파일 및 클라이언트 링크만으로 변경 사항을 선택할 수 있습니다.

  1. #define에스:

    • "글로벌"범위 / 충돌 된 사용법으로 인해 해결하기 어려운 컴파일 문제와 심각한 오류 메시지가 아닌 예기치 않은 런타임 결과가 발생할 수 있습니다. 이를 완화하려면 다음이 필요합니다.
      • 길고 불분명하고 /하거나 중앙에서 조정 된 식별자를 사용하고 이들에 액세스하면 사용 / 현재 / Koenig 조회 네임 스페이스, 네임 스페이스 별칭 등을 암시 적으로 일치시키는 이점을 얻을 수 없습니다.
      • 가장 좋은 방법은 템플릿 매개 변수 식별자를 단일 문자 대문자 (숫자 뒤에 올 수 있음)로 사용할 수 있지만 소문자없이 식별자를 사용하는 다른 방법은 일반적으로 전 처리기 정의용으로 예약되어 있으며 OS 및 C / C ++ 라이브러리 외부에서 예상됩니다 헤더). 이는 엔터프라이즈 규모의 전 처리기 사용을 관리하기 쉽게 유지하는 데 중요합니다. 타사 라이브러리를 준수해야합니다. 이를 관찰하면 기존 consts 또는 enum을 정의로 /에서 정의로 마이그레이션하면 대문자가 변경되므로 "간단한"재 컴파일이 아닌 클라이언트 소스 코드를 편집해야합니다. (개인적으로, 나는 열거의 첫 글자를 대문자로 쓰지만 consts는 아니므로 그 둘 사이에서 마이그레이션을하게 될 것입니다. 아마도 그것을 다시 생각할 시간입니다.)
    • 더 많은 컴파일 타임 연산 가능 : 문자열 리터럴 연결, 문자열 화 (태킹 크기), 식별자로 연결
      • 단점은 주어진 #define X "x"클라이언트 및 일부 클라이언트 사용 ala "pre" X "post", X를 상수 대신 런타임 변경 가능 변수로 만들거나 필요로하는 경우 재 컴파일이 아닌 클라이언트 코드를 강제로 편집하는 반면, 전환은 a const char*또는 const std::string주어진 것 보다 쉽습니다. 이미 (예를 들어, 연결 작업을 통합 사용자 강제 "pre" + X + "post"string)
    • sizeof정의 된 숫자 리터럴에서 직접 사용할 수 없습니다
    • 형식화되지 않음 (와 비교하면 GCC에서 경고하지 않음 unsigned)
    • 일부 컴파일러 / 링커 / 디버거 체인은 식별자를 표시하지 않을 수 있으므로 "매직 숫자"(문자열 등)를 보는 것으로 줄어 듭니다.
    • 주소를 취할 수 없습니다
    • #define이 생성 된 상황에서 각 사용 시점에서 평가되므로 대체 값이 합법적이거나 불연속적일 필요는 없으므로 아직 선언되지 않은 객체를 참조 할 수 있으며 "구현"에 의존하지 않아도됩니다 사전 포함, "상수"등의 생성 { 1, 2 }이이 초기화 배열에 사용하거나 할 수있다 #define MICROSECONDS *1E-6등 ( 확실히 이 추천하지!)
    • 매크로 대체 __FILE__와 같은 특별한 것들__LINE__
    • #if조건부로 코드 (전 처리기에서 선택하지 않은 경우 코드를 컴파일 할 필요가 없으므로 후 처리 "if"보다 강력 함)를 포함하는 명령문의 존재 및 값을 테스트하고 #undef-ine, redefine 등을 사용할 수 있습니다 .
    • 대체 된 텍스트가 노출되어야합니다.
      • 번역 단위에서 사용됩니다. 즉, 클라이언트 사용을 위해 라이브러리의 매크로가 헤더에 있어야하므로 make다른 타임 스탬프 기반 재 컴파일 도구가 변경되면 클라이언트 재 컴파일을 트리거합니다 (나쁜!)
      • 또는 클라이언트 코드를 다시 컴파일하기 위해 더 많은주의가 필요한 명령 행 (예 : 정의를 제공하는 Makefile 또는 스크립트가 종속성으로 나열되어야 함)

내 개인적인 의견 :

일반적으로, 나는 consts를 사용 하고 일반적인 사용법에 가장 전문적인 옵션이라고 생각합니다 (다른 사람들은이 오래된 게으른 프로그래머에게 단순함을 호소합니다).


1
멋진 답변입니다. 하나의 작은 니트 : 때로는 작은 상태 머신과 같이 코드를 명확하게하기 위해 헤더에없는 로컬 열거 형을 사용합니다. 따라서 항상 헤더에있을 필요는 없습니다.
kert

찬반 양론이 뒤섞여 비교표를보고 싶습니다.
Unknown123

@ Unknown123 : 하나를 게시하십시오-여기에서 가치가 있다고 생각하는 점을 찢어도 상관 없습니다. 건배
토니 델로이

48

이것이 C ++ 질문이고 #define대안으로 언급 되면 클래스 멤버가 아니라 "전역"(예 : 파일 범위) 상수에 관한 것입니다. C ++에서 이러한 상수에 관해서 static const는 중복됩니다. C ++에서는 const기본적으로 내부 연결이 있으며 선언 할 필요가 없습니다 static. 그래서 그것은 실제로 const#define입니다.

그리고 마지막으로 C ++ const에서 선호됩니다. 적어도 그러한 상수는 유형이 지정되고 범위가 지정되기 때문입니다. 선호하는 단순히 아무 이유가없는 #define이상 const몇 가지 예외 제외하고는.

문자열 상수 (BTW)는 이러한 예외의 한 예입니다. 함께 #defineD 문자열 상수 하나 같이 컴파일러는 C / C ++의 컴파일 타임 연결 기능을 사용할 수 있습니다

#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"

const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;

추신 : 누군가를 static const대신 하여을 언급 할 때 #define일반적으로 C ++이 아니라 C에 대해 이야기하고 있음을 의미합니다. 이 질문에 올바르게 태그가 지정되어 있는지 궁금합니다.


1
" #define을 선호 할 이유가 없습니다. " 헤더 파일에 정의 된 정적 변수?
curiousguy

9

#define 예기치 않은 결과가 발생할 수 있습니다.

#include <iostream>

#define x 500
#define y x + 5

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

잘못된 결과를 출력합니다.

y is 505
z is 510

그러나 이것을 상수로 바꾸면 :

#include <iostream>

const int x = 500;
const int y = x + 5;

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

올바른 결과를 출력합니다.

y is 505
z is 1010

#define텍스트를 단순히 대체 하기 때문 입니다. 이렇게하면 작업 순서가 심각하게 엉망이 될 수 있으므로 대신 상수 변수를 사용하는 것이 좋습니다.


1
나는 다른 예기치 않은 결과를 가지고 : y값이 있었다 5500의 리틀 엔디안 연결 x, 5
망치로 코드

5

정적 const를 사용하는 것은 코드에서 다른 const 변수를 사용하는 것과 같습니다. 즉, 사전 컴파일 과정에서 코드에서 간단히 대체되는 #define과 달리 정보의 출처를 추적 할 수 있습니다.

이 질문에 대한 C ++ FAQ Lite를 살펴볼 수 있습니다. http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7


4
  • 정적 const는 유형이 있으며 유형이 있으며 유효성, 재정의 등을 컴파일러에서 확인할 수 있습니다.
  • #define은 정의되지 않은 것으로 재정의 될 수 있습니다.

일반적으로 정적 const를 선호해야합니다. 단점이 없습니다. 프리 프로세서는 주로 조건부 컴파일에 사용되어야합니다 (때로는 더러워진 trics에 사용).


3

처리기 지시문을 사용하여 상수를 정의하는 것은 #define에뿐만 아니라 적용하는 것은 권장하지 않습니다 C++,하지만로도 C. 이 상수에는 유형이 없습니다. 상수 에도 C사용하도록 제안되었습니다 const.



2

항상 전처리 기와 같은 일부 추가 도구보다 언어 기능을 사용하는 것을 선호합니다.

ES.31 : 상수 또는 "함수"에 매크로를 사용하지 마십시오

매크로는 버그의 주요 원인입니다. 매크로는 일반적인 범위 및 유형 규칙을 따르지 않습니다. 매크로는 인수 전달에 대한 일반적인 규칙을 따르지 않습니다. 매크로는 인간 독자가 컴파일러가 보는 것과 다른 것을 보게합니다. 매크로는 도구 제작을 복잡하게 만듭니다.

에서 C ++ 핵심 가이드 라인


0

클래스의 모든 인스턴스에서 공유 할 상수를 정의하는 경우 정적 const를 사용하십시오. 상수가 각 인스턴스에 고유 한 경우 const 만 사용하십시오 (그러나 클래스의 모든 생성자는 초기화 목록에서이 const 멤버 변수를 초기화해야합니다).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.