2013-02-11 2 views
7

컨테이너 클래스 (예 : std::vector)에는 컨테이너의 크기 (즉, 크기)와 요소의 고정 된 개념이라는 두 가지 개념이 있습니다. std::vector이 두 가지를 혼동 것 같습니다 같은 다음과 같은 간단한 코드는 컴파일되지 않습니다 :에 데이터 멤버 (세 개의 포인터가 데이터와 말 & 말을 시작하더라도const와 컨테이너의 내용이 아닌 const와 그 내용

struct A { 
    A(size_t n) : X(n) {} 
    int&x(int i) const { return X[i]; } // error: X[i] is non-const. 
private: 
    std::vector<int> X; 
}; 

주 할당 된 버퍼의 수)가 인 경우 이 아니며 operator[]을 호출하여 변경 한 경우이 아니라면이 멤버는 const이 아닌 것입니까?

참고 또한 원시 포인터를 들면 일정한 네스의 두 개념이 깔끔하게 이러한

struct B { 
    B(size_t n) : X(new int[n]) {} 
    ~B() { delete[] X; } 
    void resize(size_t n);     // non-const 
    int&x(int i) const { return X[i]; } // fine 
private: 
    int*X; 
}; 

작품 잘 대응하는 원시 코드 포인터 것을 분리한다.

따라서 std::vector (mutable을 사용하지 않고)을 사용할 때이를 처리하는 올바른/권장 방법은 무엇입니까?

(여기서는, 그래서 UB X가 비 const 알려져있다) 허용 가능한 것으로 간주 const_cast<>

int&A::x(int i) const { return const_cast<std::vector<int>&>(X)[i]; } 

에서처럼입니까?

편집 그냥 더 혼란을 방지하기 위해 : 나는 요소, 즉 컨테이너의 내용 만 수정하고 싶어하지 컨테이너 자체 (크기 및/또는 메모리 위치).

+0

'std :: vector'의 데이터는'operator []'를 호출하여 변경할 수 있습니다. 'A :: X'를 작성 했으므로'a.x (1) ++; '는 완벽하게 합법적이며 벡터의 내용을 수정합니다. –

+1

@DavidSchwartz 벡터의 * 내용 *은 실제 * 데이터 *가 아닙니다 (논리적으로 연결할 수는 있지만). 'std :: vector'를 검사하면, 데이터로서 3 개의 포인터 만 있습니다 (데이터의 시작과 끝, 버퍼의 끝). 이들은 변함없이 유지됩니다. – Walter

답변

13

C++은 한 수준 인 const 만 지원합니다. 실제로 (즉, sizeof에서 계산)를 객체의 "비트" 게임을 (const_cast 등)없이 변경 될 수 있지만, 다른 아무것도 게임 공정한 : 지금까지 컴파일러 에 관한 한, 그것은 비트 CONST입니다 . C++ (1980 년대 후반, 1990 년대 초반)의 초창기에 은 Bitwise의 디자인 이점에 대해 많은 논의가있었습니다. Andy Koenig이 한 번 말했기 때문에 Humpty-Dumpty const라고도하는 const (논리적 const : 라고도 함) 프로그래머가 const을 사용하면 프로그래머가 원하는 것을 정확히 의미합니다. 합의가 마침내 논리적 인 const에 찬성하여 합쳐졌습니다.

이것은 컨테이너 클래스 작성자가 을 선택해야한다는 것을 의미합니다. 컨테이너의 요소가 컨테이너의 일부인지 여부입니다. 컨테이너의 일부인 경우 컨테이너가 const 인 경우 을 수정할 수 없습니다. 선택할 수있는 방법이 없습니다. 컨테이너 작성자는 하나를 선택하거나 다른 컨테이너를 선택해야합니다. 여기에도 합의가있는 것으로 보입니다. 요소는 컨테이너의 일부이고, 컨테이너가 const 인 경우 수정할 수 없습니다. ( C 스타일 배열로 아마 병렬 여기에 역할을, C 형식의 배열이 CONST 인 경우, 는 다음 요소 중 하나를 수정할 수 없습니다.)

당신처럼, 나는 금지 싶어 할 때 내가 번 발생했습니다 (아마도 반복자를 보호하기 위해) 벡터의 크기는 수정하지만 요소는 수정하지 않습니다. 만족스러운 해결책은 실제로는 없습니다. 내가 생각할 수있는 가장 좋은 점은 mutable std::vector을 포함하는 새로운 유형을 만들고 const 의미에 해당하는 전달 기능을 제공하는 것입니다.이 특별한 경우에 필요합니다. 그리고 만약 당신이 3 단계 (완전 const, 부분 const, 비 const)를 구별하고 싶다면 파생이 필요합니다. 기본 클래스는 만 완전히 const 및 부분 const 함수 (예 : const int operator[](size_t index) const;int operator[]( size_t index);)를 노출하지만, void push_back(int);은 표시하지 않습니다. 요소 삽입 및 제거를 허용하는 함수는 파생 클래스에만 노출되는 입니다. 요소를 삽입하거나 제거하지 말아야하는 클라이언트는 기본이 아닌 참조 만 전달됩니다.

+0

+1 멋진 토론. 컨테이너 클래스의 디자이너가 선택의 여지가 없다는 사실에 대해서는 확신하지 못합니다. 템플릿 인수로서 그 요소의 const-ness를 운반하고 const와 non-const 컨테이너 사이에서 (스마트 포인터를 사용하여) 이동 변환을 허용하는 컨테이너 클래스를 구현할 수 있습니다. 하지만 기본 파생 디자인은 더 단순 해 보입니다. – Walter

+0

@Walter 컨테이너 클래스 디자이너는 모든 종류의 선택이 가능합니다. 'std :: vector'의 디자이너는,이 경우, 일반적인 합의가있는 것처럼 보입니다 --- 제가 알고있는 대부분의 사전 표준 컨테이너가 동일하게 만들어졌습니다. 전 세계적으로 특수한 경우 (예를 들어'std :: array')를 제외하고는 세 가지 수준의'const' (none, partial 또는 complete)를 제공하는 컨테이너에 대한 큰 요구가없는 것 같습니다. (표준 전 일 동안, 배열 클래스 중 하나가 생성자 인수로 크기를 가져 왔고 나중에 변경할 가능성이 없습니다.) –

+0

@JamesKanze : 설명은 훌륭하지만 처음 맨 처음 문장은 bitwise const가 C++에서 지원한다는 인상. * (TL이 너무 많은 C++ 프로그래머가 없기를 바랍니다.) *이 시작 문장은 컴파일러 백엔드에도 적용되지만 프론트 엔드는 구문 제약을'const'로 간주합니다. 형식 검사에 사용됩니다. (그런데 C++ 11에서는 컴파일 타임'constexpr'을 도입했습니다.) 논리적 인 constance는 프로그래머의 두뇌에만 존재합니다. C++ 프로그래머는 멀티 스레드 프로그래밍을 시작할 때 차이점을 인식합니다. – rwong

3

그것은 이상한 디자인이 아니며, 매우 신중한 선택이며, 올바른 IMHO입니다.

귀하의 B 예는 std::vector에 대한 좋은 비유 아니라, 더 좋은 비유는 다음과 같습니다

struct C { 
    int& get(int i) const { return X[i]; } 
    int X[N]; 
}; 

하지만 배열의 크기를 조정할 수있는 매우 유용한 차이

. 위의 코드는 원본과 동일한 이유로 유효하지 않습니다. 배열 (또는 vector) 요소는 개념적으로 포함 된 유형의 "멤버"(기술적으로 하위 오브젝트)이므로 const 멤버를 통해 수정할 수 없습니다. 기능.

나는 const_cast이 허용 가능하지 않으며 최후의 수단이 아니면 mutable을 사용하고 있지 않습니다. 왜 const 오브젝트의 데이터를 변경하고, 멤버 함수를 const가 아닌 것으로 만드는 지 고려해야합니다.

+0

이것은 해석의 문제입니다. 'vector'를 크기 조절이 가능한 C 배열로 보면, 나는 동의한다. 그렇다면'resize()'등의 함수는 어디에서 유추 할 수 있을까? 그것들은 일정 이상이어야합니다 : 여러분은 요소에 대한 비 const 액세스를 허용 할 수 있기를 원하지만 크기에 대한 접근은 불가능해야합니다. 이것은'std :: vector'에서는 가능하지 않습니다. 이것이 보통 일종의 해결 방법이라고 생각합니다. 반복자를 제공하는 것입니다. 일반적으로 내용을 변경할 수는 있지만 컨테이너는 변경할 수 없습니다. – Walter

+0

그것은 단지 비유 일뿐입니다. 문자 그대로 사용하지 마십시오. 어쨌든'resize'는 const가 아니므로 객체를 변형 할 수 있습니다. 반복자에 대해 무엇을 의미하는지 확신 할 수 없지만 표준 컨테이너 중 어느 것도 const 컨테이너에 대한 비 const 반복자를 제공하지 않습니다. –

+0

iterators에 대한 의미는 (non-const) iterator가 있으면 컨테이너를 수정할 수 없다는 것입니다. 따라서 컨테이너는 const를 유지하면서 요소는 수정할 수 있습니다. – Walter

4

불행하게도, 포인터와는 달리, 당신은 그들이 적용될 수로 std::vectorconst 두 종류의 사이에 명확하게 할 수없는 이유

std::vector<int> i; 
std::vector<const int>& ref = i; 

뭔가를 할 수 없으며 보수적이어야한다.나는 개인적으로,

const_cast<int&>(X[i]); 

편집처럼 뭔가를 선택할 것 : 다른 주석이 정확하게 지적한 바와 같이, 반복자 모델이 이분법을한다. vector<int>::iterator을 처음에 저장 한 경우 const 메소드에서 해당 참조를 역 참조하여 비 const int&으로 되돌릴 수 있습니다. 나는 생각한다. 그러나 무효화에주의해야합니다.

+1

반복자를 사용하면 재미있는 해결책을 알 수 있습니다. _view_ 클래스는 시작과 끝 반복자를 포함하며 원하는 제한된 기능 만 제공합니다. (그 점에 관해서는, 벡터에 대한 포인터를 가진 뷰 클래스도 마찬가지입니다.) –

-1

const_cast 대신 std::vector::at() 메서드를 사용하는 것이 좋습니다.

+0

'vector :: at()'의 const 과부하가'const' 참조를 반환하므로 도움이되지 않습니다 –

관련 문제