표준 기음++
- 이 문제를 해결하는 데 도움이되는 몇 가지 방법이 있습니다.
- “정확성”은 일반 유형 안전성과 어떻게 관련이 있습니까?
- “빨리”또는”나중에”올바른 것을 얻으려고해야합니까?
- &
- “엑스&콘스트 엑스”가 의미가 있습니까?
- “구성 멤버 함수”란 무엇입니까?
- 참조별 반환 함수와 상수 멤버 함수 간의 관계는 무엇입니까?
- “오버로드”와 관련된 거래는 무엇입니까?
- 논리 상태와 물리적 상태를 구별하면 더 나은 클래스를 설계하는 데 어떻게 도움이 될 수 있습니까?
- 내 공용 멤버 함수의 불변성은 메서드가 개체의 논리 상태 또는 물리적 상태를 기반으로 해야 합니까?
- 상수 멤버 함수가 데이터 멤버에”보이지 않는”변경을 하려면 어떻게 해야 합니까?
- 콘스캐스트는 최적화 기회 손실을 의미합니까?
- 왜 컴파일러는 내가 정수로 지적한 후에 정수를 변경할 수 있습니까?
이 문제를 해결하는 데 도움이되는 몇 가지 방법이 있습니다.
좋은 일. 즉,const
라는 키워드를 사용하여const
개체가 변이하는 것을 방지합니다.
예를 들어std::string
을 수락하는f()
함수를 만들고 호출자에게f()
에 전달되는 호출자의std::string
을 변경하지 않겠다고 약속하려는 경우f()
이std::string
매개 변수를 받을 수 있습니다…
-
void f1(const std::string& s);
// 참조-에 의해 전달-const
-
void f2(const std::string* sptr);
// 포인터 전달-const
-
void f3(std::string s);
// 값 전달
const
참조 전달 및const
포인터 전달의 경우f()
함수 내에서 호출자의std::string
을 변경하려는 시도는 컴파일러에서 오류로 플래그가 지정됩니다 컴파일 타임. 이 검사는 컴파일 타임에 완전히 수행됩니다:const
에 대한 런타임 공간 또는 속도 비용이 없습니다. 값 전달 사례(f3()
)에서 호출된 함수는 호출자의std::string
의 복사본을 가져옵니다. 즉,f3()
은 로컬 복사본을 변경할 수 있지만f3()
이 반환되면 복사본이 삭제됩니다. 특히f3()
은 호출자의std::string
개체를 변경할 수 없습니다.
반대의 예로std::string
을 허용하는 함수g()
를 만들고 싶지만g()
가 호출자의std::string
개체를 변경할 수 있음을 호출자에게 알려야 한다고 가정합니다. 이 경우g()
는std::string
매개 변수를 받을 수 있습니다…
-
void g1(std::string& s);
// 참조-투-비에 의해 전달-const
-
void g2(std::string* sptr);
// 비 포인터에 의해 전달-const
이 함수에서const
가 부족하면 컴파일러에서 호출자의std::string
개체를 변경할 수는 있지만 필수는 아님을 알 수 있습니다. 따라서std::string
을f()
함수에 전달할 수 있지만f3()
(매개 변수를”값으로”수신하는 함수)만std::string
을g1()
또는g2()
로 전달할 수 있습니다. f1()
또는f2()
이g()
함수를 호출해야 하는 경우std::string
개체의 로컬 복사본을g()
함수로 전달해야 합니다. 예:.,
void g1(std::string& s);void f1(const std::string& s){ g1(s); // Compile-time Error since s is const std::string localCopy = s; g1(localCopy); // Okay since localCopy is not const}
위의 경우 당연히g1()
가 변경한 내용은f1()
에 로컬인localCopy
개체에 적용됩니다.특히f1()
을 참조하여 전달한const
매개 변수는 변경되지 않습니다.
“정확성”은 일반 유형 안전성과 어떻게 관련이 있습니까?
매개 변수의const
다움을 선언하는 것은 형식 안전의 또 다른 형태 일뿐입니다.
일반적인 유형 안전이 올바른 시스템을 얻는 데 도움이된다는 것을 알게되면(특히 대형 시스템에서)const
정확성도 도움이됩니다.
const
정확성의 이점은 예상하지 못한 것을 실수로 수정하는 것을 방지한다는 것입니다.수정 될 것입니다. 컴파일러와 다른 프로그래머에게 중요한 의미 정보(컴파일러가 실수를 방지하기 위해 사용하고 다른 프로그래머가 문서로 사용하는 정보)를 추가로 알려주는 혜택과 함께 몇 가지 추가 키 입력(const
키워드)으로 코드를 장식해야합니다.
개념적으로const std::string
는 예를 들어const
변형이 개념적으로const
이 아닌 변형에서 사용할 수 있는 다양한 변경 연산이 없기 때문에std::string
과 다른 클래스라고 상상할 수 있습니다. 예를 들어,개념적으로const std::string
에는 단순히 할당 연산자+=
또는 기타 변경 연산이 없다고 상상할 수 있습니다.
“빨리”또는”나중에”올바른 것을 얻으려고해야합니까?
아주,아주,아주 처음에.
백 패치const
정확성은 눈덩이 효과를 초래합니다:const
를 추가 할 때마다”여기에”추가 할 4 개가 더 필요합니다.”
const
를 일찍 자주 추가하십시오.
는p
이X
클래스의 객체를 가리킨다는 것을 의미하지만p
은X
객체를 변경하는 데 사용할 수 없습니다(당연히p
도NULL
이 될 수 있음).
오른쪽에서 왼쪽으로 읽기:”피 에 대한 포인터입니다 엑스 상수.”
예를 들어 클래스X
에inspect() const
과 같은const
멤버 함수가 있으면p->inspect()
이라고해도 괜찮습니다. 그러나 클래스X
이mutate()
라는const
멤버 함수가 아닌 경우p->mutate()
이라고 말하면 오류가 발생합니다.
이 오류는 컴파일 타임에 컴파일러에 의해 발견되므로 런타임 테스트가 수행되지 않습니다. 즉,const
는 프로그램 속도를 늦추지 않으며 런타임에 검사하기 위해 추가 테스트 케이스를 작성할 필요가 없습니다.이 문제를 해결하려면 다음 단계를 수행하십시오.
포인터 선언을 오른쪽에서 왼쪽으로 읽습니다.
-
const X* p
“p
이const
인X
을 가리킨다는 의미:X
객체는p
을 통해 변경할 수 없습니다. -
X* const p
은”p
은const
가 아닌X
에 대한const
포인터입니다.”: 포인터p
자체는 변경할 수 없지만p
을 통해X
개체를 변경할 수 있습니다. -
const X* const p
은”p
은const
인X
에 대한const
포인터입니다.”를 의미합니다.
그리고 오,네,포인터 선언을 오른쪽에서 왼쪽으로 읽는 것을 언급 했습니까?
&
는x
별칭을X
객체로 의미하지만x
을 통해X
객체를 변경할 수 없습니다.
오른쪽에서 왼쪽으로 읽기: “x
은const
인X
에 대한 참조입니다.”
예를 들어 클래스X
에inspect() const
과 같은const
멤버 함수가 있으면x.inspect()
이라고해도 괜찮습니다. 그러나X
클래스에mutate()
라는 비const
멤버 함수가 있으면x.mutate()
라고 말하면 오류입니다.즉,const
는 프로그램 속도를 늦추지 않으며 런타임에 검사하기 위해 추가 테스트 케이스를 작성할 필요가 없습니다.이 문제를 해결하려면 다음을 수행하십시오.
X const& x
는const X& x
와 같고X const* x
은const X* x
과 같습니다.
어떤 사람들은const
-온-더-오른쪽 스타일을 선호,그것을 호출”일관된const
“또는,사이먼 브랜드에 의해 만들어 낸 용어를 사용하여,”동쪽const
.”실제로”동쪽const
“스타일은 항상const
가 동의하는 오른쪽에 삽입되는 반면,다른 스타일은 때때로const
를 왼쪽에,때로는 오른쪽에 삽입합니다(const
포인터 선언 및const
멤버 함수의 경우).
“동쪽const
“스타일을 사용하면const
인 지역 변수가 오른쪽int const a = 42;
에const
로 정의됩니다. 마찬가지로const
인static
변수는static double const x = 3.14;
로 정의됩니다.기본적으로 모든const
는 오른쪽에 필요한const
포인터 선언과const
멤버 함수를 포함하여const
가 동의하는 오른쪽에 끝납니다.
“동쪽const
“스타일도 유형 별칭과 함께 사용할 때 덜 혼란 스럽습니다.
using X_ptr = X*;const X_ptr foo;const X* bar;
“동쪽const
“스타일을 사용하면 더 명확 해집니다.:
using X_ptr = X*;X_ptr const foo;X* const foobar;X const* bar;
foo
과foobar
은 동일한 유형이고bar
는 다른 유형이라는 것이 더 명확합니다.
“동쪽const
“스타일도 포인터 선언과 더 일치합니다. 전통적인 스타일을 대조:
const X** foo;const X* const* bar;const X* const* const baz;
“동쪽const
“스타일
X const** foo;X const* const* bar;X const* const* const baz;
이러한 이점에도 불구하고const
오른쪽 스타일은 아직 인기가 없으므로 레거시 코드는 전통적인 스타일을 갖는 경향이 있습니다.
“엑스&콘스트 엑스”가 의미가 있습니까?
아니,말도 안돼.
위의 선언이 무엇을 의미하는지 알아 보려면 오른쪽에서 왼쪽으로 읽으십시오: “x
은X
에 대한const
참조입니다.” 참조는 항상const
입니다. 절대. 또는const
없이.
즉,”X& const x
“은 기능적으로”X& x
“과 동등하다. &
뒤에const
를 추가하면 아무 것도 얻지 못하기 때문에 추가해서는 안됩니다:사람들을 혼란스럽게 할 것입니다—const
는 어떤 사람들이X
이const
라고 생각하게 만듭니다.
“구성 멤버 함수”란 무엇입니까?
개체를 변형시키는 대신 검사하는 멤버 함수입니다.
const
멤버 함수는 멤버 함수의 매개 변수 목록 바로 뒤에const
접미사로 표시됩니다. const
접미사가 있는 멤버 함수를”const
멤버 함수”또는”검사기”라고 합니다.”const
접미사가 없는 멤버 함수를”const
이 아닌 멤버 함수”또는”뮤 테이터”라고 합니다.”
class Fred {public: void inspect() const; // This member promises NOT to change *this void mutate(); // This member function might change *this};void userCode(Fred& changeable, const Fred& unchangeable){ changeable.inspect(); // Okay: doesn't change a changeable object changeable.mutate(); // Okay: changes a changeable object unchangeable.inspect(); // Okay: doesn't change an unchangeable object unchangeable.mutate(); // ERROR: attempt to change unchangeable object}
unchangeable.mutate()
를 호출하려는 시도는 컴파일 타임에 잡힌 오류입니다. const
에 대한 런타임 공간 또는 속도 페넌트가 없으며 런타임에 테스트 케이스를 작성하여 테스트 케이스를 검사 할 필요가 없습니다.
inspect()
멤버 함수의 후행const
는 메서드가 개체의 추상(클라이언트 표시)상태를 변경하지 않음을 의미하는 데 사용해야 합니다. 이 메서드가 개체의struct
의”원시 비트”를 변경하지 않는다고 말하는 것과는 약간 다릅니다. 컴파일러는 일반적으로 해결할 수없는 앨리어싱 문제를 해결할 수 없다면”비트 단위”해석을 취할 수 없습니다(즉,const
가 아닌 별칭이 존재할 수 있음). 이 앨리어싱 문제의 또 다른(중요한)통찰력: const
의 포인터를 가진 객체를 가리키는 것은 객체가 변경되지 않는다는 것을 보장하지 않습니다.
참조별 반환 함수와 상수 멤버 함수 간의 관계는 무엇입니까?
검사기 메서드에서 참조하여this
개체의 멤버를 반환하려면 참조 대 상수(const X& inspect() const
)또는 값(X inspect() const
)을 사용하여 반환해야 합니다.
class Person {public: const std::string& name_good() const; // Right: the caller can't change the Person's name std::string& name_evil() const; // Wrong: the caller can change the Person's name int age() const; // Also right: the caller can't change the Person's age // ...};void myCode(const Person& p) // myCode() promises not to change the Person object...{ p.name_evil() = "Igor"; // But myCode() changed it anyway!!}
좋은 소식은 당신이 잘못하면 컴파일러가 종종 당신을 잡을 것입니다. 특히,위의Person::name_evil()
에서와 같이const
가 아닌 참조로this
개체의 구성원을 실수로 반환하는 경우 컴파일러는 종종 해당 개체를 감지하고 이 경우Person::name_evil()
의 내장을 컴파일하는 동안 컴파일 타임 오류를 제공합니다.
나쁜 소식은 컴파일러가 항상 당신을 잡을 수 없다는 것입니다:컴파일러가 단순히 컴파일 타임 오류 메시지를 제공하지 않는 경우가 있습니다.
번역:당신은 생각해야합니다. 저것이 너를 위협하면,일 다른 선을 발견하십시요;”생각하십시요”은 4 편지 낱말이 아니다.
기억”const
철학”이 섹션에 걸쳐 확산:const
멤버 함수는 변경(또는 호출자 변경 허용)this
개체의 논리 상태(일명 추상 상태 일명 의미 상태). 객체가 무엇을 의미하는지,내부적으로 구현되는 방식이 아니라 어떻게 구현되는지 생각해보십시오. 사람의 나이와 이름은 논리적입니다.그 사람의 일부이지만 그 사람의 이웃과 고용주는 그렇지 않습니다. this
개체의 논리적/추상/의미적 상태의 일부를 반환하는 검사기 메서드는const
가 아닌 포인터(또는 참조)를 해당 부분에 반환해서는 안되며,해당 부분이 내부적으로this
개체 또는 다른 방식으로 물리적으로 내장 된 직접 데이터 멤버로 구현되는지 여부에 관계없이 반환해야합니다.
“오버로드”와 관련된 거래는 무엇입니까?
const
오버로드는const
정확성을 달성하는 데 도움이됩니다.
const
오버로드는 인스펙터 메서드와 뮤터 메서드가 같은 이름과 매개 변수의 수와 유형이 같은 경우입니다. 두 가지 다른 방법은 인스펙터가const
이고 뮤 테이터가const
이 아니라는 점에서만 다릅니다.
const
오버로딩의 가장 일반적인 사용은 아래 첨자 연산자입니다. 아래 첨자 연산자는 쌍으로 오는 경우가 많습니다.
class Fred { /*...*/ };class MyFredList {public: const Fred& operator (unsigned index) const; // Subscript operators often come in pairs Fred& operator (unsigned index); // Subscript operators often come in pairs // ...};
const
아래 첨자 연산자는const
참조를 반환하므로 컴파일러는 호출자가 실수로Fred
를 변경/변경하지 못하도록 합니다. const
가 아닌 아래 첨자 연산자는const
가 아닌 참조를 반환합니다.이 참조는 호출자(및 컴파일러)에게 호출자가Fred
개체를 수정할 수 있음을 알리는 방법입니다.
MyFredList
클래스의 사용자가 아래 첨자 연산자를 호출할 때 컴파일러는MyFredList
의 불변성에 따라 호출할 오버로드를 선택합니다. 호출자에MyFredList a
또는MyFredList& a
이 있으면a
는const
가 아닌 아래 첨자 연산자를 호출하고 호출자는const
가 아닌 참조로 끝납니다.Fred
:
예를 들어class Fred
에 검사기 방법inspect() const
과 뮤 테이터 방법mutate()
이 있다고 가정합니다:
void f(MyFredList& a) // The MyFredList is non-const{ // Okay to call methods that inspect (look but not mutate/change) the Fred at a: Fred x = a; // Doesn't change to the Fred at a: merely makes a copy of that Fred a.inspect(); // Doesn't change to the Fred at a: inspect() const is an inspector-method // Okay to call methods that DO change the Fred at a: Fred y; a = y; // Changes the Fred at a a.mutate(); // Changes the Fred at a: mutate() is a mutator-method}
그러나 호출자에const MyFredList a
또는const MyFredList& a
가 있으면a
는const
첨자 연산자를 호출하고 호출자는Fred
에 대한const
참조로 끝납니다. 이를 통해 호출자는a
에서Fred
를 검사 할 수 있지만 호출자가 실수로a
에서Fred
를 변경/변경하는 것을 방지합니다.
void f(const MyFredList& a) // The MyFredList is const{ // Okay to call methods that DON'T change the Fred at a: Fred x = a; a.inspect(); // Compile-time error (fortunately!) if you try to mutate/change the Fred at a: Fred y; a = y; // Fortunately(!) the compiler catches this error at compile-time a.mutate(); // Fortunately(!) the compiler catches this error at compile-time}
아래 첨자 및 펑콜 연산자에 대한 오버로딩이 여기,여기,여기,여기 및 여기에 설명되어 있습니다.
물론const
-아래 첨자 연산자 이외의 항목에 대해 오버로드를 사용할 수도 있습니다.
논리 상태와 물리적 상태를 구별하면 더 나은 클래스를 설계하는 데 어떻게 도움이 될 수 있습니까?
이는 클래스와 객체를 이해하고 사용하기 쉽고,직관적이며,오류가 적고,더 빨리 만들 수 있도록 내부 아웃보다는 외부에서 클래스를 디자인하도록 장려하기 때문입니다. (좋아,그것은 약간의 지나치게 단순화입니다. 이 모든 것을 이해하려면,이 대답의 나머지 부분을 읽어야 할 것입니다!)
의 내부 아웃에서 이것을 이해하자-당신은(해야)외부-에서 클래스를 디자인하지만,이 개념을 처음 사용하는 경우,내부 아웃에서 이해하는 것이 더 쉽습니다.
내부에서 객체는 물리적(또는 콘크리트 또는 비트 단위)상태를 갖습니다. 이 상태는 프로그래머가보고 이해하기 쉬운 상태입니다.
바깥쪽에는 객체에 클래스 사용자가 있으며 이러한 사용자는public
멤버 함수 및friend
초만 사용하도록 제한됩니다. 이러한 외부 사용자 개체 클래스Rectangle
메서드width()
,height()
및area()
의 경우 사용자 말할 것 이다 이러한 세 개체의 논리적(또는 추상 또는 의미)상태의 모든 부분 상태를 갖는 것으로 개체를 인식 합니다. 외부 사용자에게Rectangle
객체는 해당 영역이 즉석에서 계산되는 경우에도 실제로 영역을 갖습니다(예:area()
메서드가 개체의 너비와 높이의 곱을 반환하는 경우). 사실,그리고 이것은 중요 한 포인트,사용자 모르는 고 상관 없어 어떻게 이러한 메서드를 구현; 사용자는 여전히 자신의 관점에서 객체가 논리적으로 너비,높이 및 면적의 의미 적 상태를 가지고 있음을 인식합니다.
area()
예제에서는 논리 상태가 물리적 상태에서 직접 실현되지 않는 요소를 포함할 수 있는 경우를 보여 줍니다. 클래스는 때때로 의도적으로 객체의 물리적(구체적,비트 단위)상태의 일부를 사용자로부터 숨 깁니다-의도적으로 사용자가 읽거나 쓰거나 심지어 숨겨진 상태에 대해 알 수있는public
멤버 함수 또는friend
를 제공하지 않습니다. 즉,개체의 물리적 상태에는 개체의 논리 상태에 해당 요소가 없는 비트가 있습니다.
이 후자의 경우의 예로,컬렉션 개체는 다음 조회의 성능을 향상시키기 위해 마지막 조회를 캐시 할 수 있습니다. 이 캐시는 확실히 개체의 물리적 상태의 일부이지만 내부 구현 세부 사항은 사용자에게 노출되지 않을 것입니다. 당신이 외부에서 생각하는 경우 쉽게 무엇을 무엇을 말하고-에서: 컬렉션 개체의 사용자가 캐시 자체의 상태를 확인할 수 있는 경우 캐시는 투명하며 개체의 논리 상태에 포함되지 않습니다.
내 공용 멤버 함수의 불변성은 메서드가 개체의 논리 상태 또는 물리적 상태를 기반으로 해야 합니까?
논리.
이 다음 부분을 쉽게 만들 수있는 방법은 없습니다. 그것은 상처 것입니다. 가장 좋은 방법은 앉는 것입니다. 그리고 제발,당신의 안전을 위해 근처에 날카로운 도구가 없는지 확인하십시오.
컬렉션 개체 예제로 돌아가 보겠습니다. 기억: 향후 조회 속도를 높이기 위해 마지막 조회를 캐시하는 조회 방법이 있습니다.
의 아마 분명 무엇을 상태 보자:조회 메서드는 컬렉션-개체의 논리 상태 중 하나를 변경하지 않는 것으로 가정합니다.
그래서… 당신은 준비가 되셨습니까?
여기 온다:조회 메서드는 컬렉션 개체의 논리 상태를 변경 하지 않습니다 하지만 컬렉션 개체의 실제 상태를 변경 하는 경우(매우 실제 캐시에 매우 실제 변경),해야루킹 메서드const
?
대답은’예’입니다. (모든 규칙에는 예외가 있으므로”예”는 그 옆에 별표가 있어야하지만 대부분의 경우 대답은’예’입니다.)
이것은”물리적const
보다”논리적const
“에 관한 것입니다.”그것은const
로 아멧드를 장식 할 것인지에 대한 결정이 그 방법이 논리적 상태를 변하지 않게 남겨 둘 것인지에 달려 있음을 의미합니다(앉아 있습니까?)(당신은 앉아서 할 수 있습니다)메소드가 객체의 실제 물리적 상태에 매우 실제적인 변화를 일으키는 지 여부에 관계없이.
침몰하지 않았거나 아직 아프지 않은 경우 두 가지 경우로 분리해 보자:
- 메서드가 개체의 논리 상태의 일부를 변경하면 논리적으로 뮤 테이터가 됩니다.)이 방법은 객체의 구체적인 상태의 물리적 비트를 변경하지 않습니다.
- 반대로,메서드는 논리적으로 검사기이며 객체의 논리 상태의 일부를 변경하지 않는 경우
const
이어야합니다(실제로 발생합니다!)이 방법은 객체의 구체적인 상태의 물리적 비트를 변경합니다.
헷갈리면 다시 읽어보세요.
당신이 혼동하지 않고 화가 나면,좋은:당신은 아직 그것을 좋아하지 않을 수 있습니다,하지만 적어도 당신은 그것을 이해. 심호흡을하고 나 후에 반복:”const
메소드의 다움은 객체 외부에서 의미가 있어야합니다.”
당신이 여전히 화가 있다면,이 세 번 반복:”방법의 불변성은 객체의 사용자에게 의미가 있어야합니다,그 사용자는 객체의 논리 상태를 볼 수 있습니다.”
당신이 여전히 화가 있다면,죄송합니다,그것은 무엇이다. 그것을 빨아 그것과 함께 살고. 예,예외가 있을 것입니다; 모든 통치자가 그들을 가지고 있습니다. 그러나 원칙적으로,이 논리적const
개념은 당신에게 좋고 당신의 소프트웨어에 좋습니다.
한 가지 더. 이것은 어리석은 일이 될 것이지만,메소드가 객체의 논리적 상태를 변경하는지 여부에 대해 정확히합시다. 클래스 외부에 있으면 일반 사용자 인 경우 수행 할 수있는 모든 실험(호출하는 모든 메소드 또는 메소드의 순서)은 해당 조회 메소드를 처음 호출했는지 여부에 관계없이 동일한 결과(동일한 반환 값,동일한 예외 또는 예외 부족)를 갖습니다. 조회 함수가 향후 메소드의 향후 동작을 변경 한 경우(더 빠르게 만드는 것이 아니라 결과 변경,반환 값 변경,예외 변경),조회 메소드는 객체의 논리 상태를 변경했습니다. 그러나 조회 방법이 아마도 어떤 것을 더 빨리 만드는 것 외에는 아무것도 변경하지 않았다면,그것은 검사자입니다.
상수 멤버 함수가 데이터 멤버에”보이지 않는”변경을 하려면 어떻게 해야 합니까?
사용mutable
(또는 최후의 수단으로const_cast
사용).
검사자의 작은 비율은 외부 사용자가 관찰 할 수없는 객체의 물리적 상태를 변경해야-물리적하지만 논리 상태가 아닌 변경.
예를 들어,앞에서 설명한 컬렉션 개체는 다음 조회의 성능을 향상시키기 위해 마지막 조회를 캐시했습니다. 이 예에서 캐시는 컬렉션 객체의 공용 인터페이스(타이밍 제외)의 어떤 부분에서도 직접 관찰 할 수 없기 때문에 그 존재와 상태는 객체의 논리적 상태의 일부가 아니므로 외부 사용자에게는 캐시의 변경 사항이 보이지 않습니다. 조회 메서드는 적어도 현재 구현의 경우 개체의 물리적 상태를 변경한다는 사실과 관계없이 개체의 논리 상태를 변경하지 않으므로 검사기입니다.
메서드가 물리적이지만 논리 상태가 아닌 상태를 변경하는 경우 메서드는 실제로 검사기 메서드이므로 일반적으로const
로 표시해야 합니다. 컴파일러에서const
메서드가this
개체의 물리적 상태를 변경하는 것을 볼 때,그것은 불평 할 것입니다-그것은 당신의 코드에 오류 메시지를 줄 것입니다.컴파일러 언어는mutable
키워드를 사용하여 이 논리적const
개념을 수용할 수 있도록 도와줍니다. 이 경우 캐시를mutable
키워드로 표시하여 컴파일러가const
메서드 내에서 또는 다른const
포인터 또는 참조를 통해 변경할 수 있음을 알 수 있습니다. 우리의 용어에서mutable
키워드는 논리 상태의 일부가 아닌 개체의 물리적 상태 부분을 표시합니다.
mutable
키워드는 데이터 멤버의 선언 바로 앞,즉const
를 넣을 수있는 동일한 위치로 이동합니다. 선호하지 않는 다른 방법은const_cast
키워드를 통해this
포인터의const
다움을 버리는 것입니다:
Set* self = const_cast<Set*>(this); // See the NOTE below before doing this!
이 줄 뒤에self
은this
,즉self == this
과 같은 비트를 갖지만self
은const Set*
이 아닌Set*
입니다(기술적으로this
은const Set* const
이지만 가장 오른쪽const
는이 논의와 관련이 없습니다).즉,self
을 사용하여this
이 가리키는 개체를 수정할 수 있습니다.
참고:const_cast
에서 발생할 수 있는 매우 드문 오류가 있습니다. 그것은 매우 희귀 한 세 가지가 동시에 결합 될 때만 발생합니다: 위의 설명과 같이mutable
이어야 하는 데이터 멤버,mutable
키워드 및/또는 이를 사용하지 않는 프로그래머를 지원하지 않는 컴파일러,원래const
로 정의한 객체(const
포인터로 가리키는 일반const
가 아닌 객체와 반대).이 조합은 매우 드물기 때문에 결코 발생하지 않을 수 있지만 코드가 작동하지 않을 수 있습니다(표준은 동작이 정의되지 않았다고 말합니다).
const_cast
을 사용하려면mutable
를 대신 사용하십시오. 즉,개체의 멤버를 변경해야 하는 경우 해당 개체가const
포인터로 가리키는 경우 가장 안전하고 간단한 방법은mutable
를 멤버 선언에 추가하는 것입니다. 실제 객체가const
가 아니라고 확신하는 경우const_cast
을 사용할 수 있지만(예:객체가Set
s;
)객체 자체가const
(예:const Set s;
과 같이 선언 될 수 있음)const_cast
이 아닌mutable
를 사용하십시오.
const
개체의mutable
멤버가 아닌 개체를 변경할 수 있습니다. 나는 상관하지 않는다—그것은 언어에 따라 불법이며 당신의 코드는 아마도 동일한 컴파일러의 다른 컴파일러 또는 심지어 다른 버전(업그레이드)에서 실패 할 것이다. 그냥 안된다고. 대신mutable
를 사용하십시오. 작동이 보장되는 코드 작성,중단되지 않는 코드가 아닙니다.
콘스캐스트는 최적화 기회 손실을 의미합니까?
이론적으로,예;실제로,아니오.
언어가const_cast
을 금지하더라도const
멤버 함수 호출에서 레지스터 캐시를 플러시하는 것을 피할 수있는 유일한 방법은 앨리어싱 문제(즉,,개체를 가리키는 비const
포인터가 없음을 증명하기 위해). 이것은 드문 경우에만 발생할 수 있습니다(개체가const
멤버 함수 호출의 범위에서 생성되고,개체의 구성과const
멤버 함수 호출 사이의 모든 비-const
멤버 함수 호출이 정적으로 바인딩되고,이러한 호출이 모두inline
디,생성자 자체가inline
디이고 멤버 함수가 생성자 호출이inline
인 경우).
왜 컴파일러는 내가 정수로 지적한 후에 정수를 변경할 수 있습니까?
“const int* p
“는”p
이*p
를 변경하지 않겠다는 약속을 의미하기 때문에*p
는 변경하지 않겠다는 약속을 의미합니다.”
const int*
이int
를 가리키도록 만드는 것은const
가 아닙니다. int
는const int*
을 통해 변경할 수 없지만 다른 사람이 같은int
를 가리키는int*
(참고:const
없음)이 있으면int*
을 사용하여int
를 변경할 수 있습니다. 예를 들어:
void f(const int* p1, int* p2){ int i = *p1; // Get the (original) value of *p1 *p2 = 7; // If p1 == p2, this will also change *p1 int j = *p1; // Get the (possibly new) value of *p1 if (i != j) { std::cout << "*p1 changed, but it didn't change via pointer p1!\n"; assert(p1 == p2); // This is the only way *p1 could be different }}int main(){ int x = 5; f(&x, &x); // This is perfectly legal (and even moral!) // ...}
main()
과f(const int*,int*)
는 다른 요일에 컴파일되는 다른 컴파일 단위일 수 있습니다. 이 경우 컴파일러가 컴파일 타임에 앨리어싱을 감지 할 수있는 방법이 없습니다. 따라서 이런 종류의 것을 금지하는 언어 규칙을 만들 수있는 방법은 없습니다. 사실,우리는 일반적으로 같은 것을 가리키는 많은 포인터를 가질 수있는 기능으로 간주되기 때문에 그러한 규칙을 만들고 싶지도 않을 것입니다. 사실 그 포인터 중 하나가 기본”것”을 변경하지 않을 것을 약속하는 것은 포인터에 의해 만들어진 약속 일뿐입니다; 그것은”일”에 의해 만들어진 약속이 아닙니다.이 문제를 해결하려면 다음을 수행하십시오.
아니요! (이것은int
포인터의 앨리어싱에 대한 자주 묻는 질문과 관련이 있습니다.)
“const Fred* p
” Fred
는 포인터p
을 통해 변경할 수 없지만const
를 거치지 않고 개체를 가져올 수있는 다른 방법이있을 수 있음을 의미합니다(예:Fred*
와 같은 별칭이 아닌const
포인터). 예를 들어,같은Fred
개체(앨리어싱)를 가리키는”const Fred* p
“및”Fred* q
“포인터가 두 개 있는 경우q
포인터를 사용하여Fred
개체를 변경할 수 있지만p
포인터는 변경할 수 없습니다.이 문제를 해결하는 데 도움이되는 몇 가지 방법이 있습니다.
Foo**
const Foo**
로 변환하는 것은 유효하지 않고 위험하기 때문입니다.
Foo*
Foo const*
(안전한)변환을 허용하지만Foo**
const Foo**
암시적으로 변환하려고 하면 오류가 발생합니다.
왜 그 오류가 좋은 지에 대한 근거가 아래에 나와 있습니다. 그러나 먼저 가장 일반적인 해결책은 다음과 같습니다.const Foo**
를const Foo* const*
으로 변경하십시오.:
class Foo { /* ... */ };void f(const Foo** p);void g(const Foo* const* p);int main(){ Foo** p = /*...*/; // ... f(p); // ERROR: it's illegal and immoral to convert Foo** to const Foo** g(p); // Okay: it's legal and moral to convert Foo** to const Foo* const* // ...}
Foo**
const Foo**
에서 변환이 위험한 이유는 캐스트없이const Foo
개체를 자동으로 실수로 수정할 수 있기 때문입니다.:
class Foo {public: void modify(); // make some modification to the this object};int main(){ const Foo x; Foo* p; const Foo** q = &p; // q now points to p; this is (fortunately!) an error *q = &x; // p now points to x p->modify(); // Ouch: modifies a const Foo!! // ...}
q = &p
줄이 합법적 인 경우q
은p
을 가리 킵니다. 다음 줄*q = &x
는p
자체를*q
이p
이기 때문에x
을 가리 키도록 변경합니다. p
은Foo*
이지만x
은const Foo
입니다. p->modify()
라인은const Foo
을 수정했기 때문에 실제 문제 인p
의 지시 대상 수정 기능을 활용합니다.
비유로,당신이 합법적 인 변장 아래 범죄자를 숨길 경우,그는 그 변장에 주어진 신뢰를 악용 할 수 있습니다.그건 나쁜.컴파일러에 의해 컴파일 타임 오류로 플래그가 지정됩니다. 알림:컴파일 타임 오류 메시지 주위에 포인터 캐스팅을하지 마십시오. 그냥 안된다고!
(참고:이것과Derived**
을Base**
으로 변환하는 것에 대한 금지 사이에는 개념적 유사성이 있습니다.)