2010-12-07 2 views
4

데이터 구조에 데이터를 삽입하기 위해 갑자기 정의 된 데이터 구조에 액세스하는 데 일반적으로 사용되는 연산자는 '나쁘다'(C++ FAQ의 의미에서) 것처럼 보입니다.왜 std :: map :: operator []가 반 직관적입니까?

문제는 '무엇이 더 좋을까요?' 이 질문은 특정 유형의 매핑 된 값에 대해 쉽게 대답됩니다. 예를 들어 키를 포인터에 매핑하면 존재하지 않는 키에 대해 operator []가 nullptr을 반환하는 것이 좋지만 다른 유형에서는 분명히 작동하지 않습니다.

존재하지 않는 키에 예외가 발생하거나 기본으로 임시 키를 생성하여지도에 추가하지 않고이를 반환 할 수 있습니다. 읽기 의미 체계에서 []를이 컨테이너 유형에 대한 의미 체계 작성으로 바꾸는 좋은 이유는 무엇입니까?

dosomething(collection[foo]); 

운영자의 정의에

collection[foo] = something; 

에서 :

+5

'operator []'는 키를 사용하여지도의 요소에 액세스하는 데 사용됩니다. 이 연산자는'operator [] '를 통해 배열이나 벡터 요소에 접근하는 것과 어떻게 다른가요? – LeopardSkinPillBoxHat

+2

이것은 내가 일반적으로 연산자 오버로드 된 메서드를 사용하지 않는 것을 선호하는 이유 중 하나입니다. 때로는 명확한 이름을 가진 메소드 (예 :'find','insert')를 사용하면보다 명확하고 읽기 쉬운 코드를 만들 수 있습니다. 다행히도 STL 컨테이너는 두 가지 모두를 제공하므로 적절한 시점에 적절한 것을 사용하도록 선택할 수 있습니다. – asveikau

+11

질문의 핵심은 'operator []'는 키가 그렇지 않다. const가 아닌 연산이된다. 이것은 매우 놀라운 일이며,'vector' 나'deque' 나'string'에서 비슷한 동작이 없습니다. –

답변

12

기본적인 문제는 확실하게 구별 할 구문 방법이 없다는 것이다. 두 위치 모두에 나타날 수 있기 때문에 클래스는 두 가지 모두를 처리 할 수 ​​있는지 확인하고 필요한 경우 덮어 쓰기 할 기본값을 제공합니다. 이것을 비양심적이라고 판단하면 std::map::operator[]을 피해야합니다.

또 다른 이유는 키가 목록에없는 경우 몇 가지 정의 된 동작이 있어야한다는 것입니다. operator[]은 값 (LValue 또는 RValue)을 반환해야하므로 null 포인터, past-the-end 반복자 또는 다른 sentinel 값을 반환 할 수 없습니다. 유일하게 남아있는 옵션은 예외를 발생시키는 것입니다. STL은 예외가없는 경우에도 사용하기 위해 많은 예외를 발생시키지 않습니다. 다른 행동을 선택해야하며 이것이 결과입니다.

가장 좋은 방법은이 동작이없는 std::map의 멤버 함수를 사용하는 것입니다. 이 값은 map::find()이고 키를 찾을 수없는 경우 map::end을 반환합니다.

+0

사실, 나는 우리가 연산자를 사용하여 수집. 도. –

+0

"std :: map :: operator [] 모두 피할 필요가 있습니다." - 또는 이미 삽입 한 키에만 사용하십시오. 결국,'vector :: operator []'에서 범위를 벗어난 인덱스를 사용하는 것은 정의되지 않은 동작이며, 사람들은 그것을 가지고 살고 있습니다. 'vector' 구현이 충분히 큰 벡터의 크기를 조정하는 것은 (매우 특이한) 유효 할 것입니다 :-) 틀림없이 표준은 '벡터'에 대해 할 수있는 일을하는 것을 방지합니다. []'디버그 빌드에서 폭발. –

+3

글쎄, case는 * std :: vector :: operator []'처럼 프록시를 반환하는 연산자 []로 구분할 수있다. 그것은 통사론적인 수준이 아니지만 그것이 필요한 이유를 알지 못합니다. 그래서, 그것은 이유가 아닙니다. 나는 단순히 새로운 요소를 할당하는 것이 더 자연스럽게 느껴졌다고 생각합니다. 결국 연관 배열 (키와 값)을 연관시키는 주된 작업이 있으므로 주요 사용법을 쉽게 표현해야합니다. 건배 & hth., –

1

TokenMacGuy가 언급했듯이 :: operator []의 사용법은 모호 할 수 있으며 설명하는 것은 모호성을 처리하는 방법입니다.

소프트웨어 개발자로서 중요한 점은 제 3 자 라이브러리가 거의 쓰여지지 않았기 때문입니다. 정확하게 사용하는 방법은입니다. 더 나쁘게 설계된 제 3 자 라이브러리는 코드의 품질을 손상시킬 수 있습니다.

std :: map 클래스를 추상화하여 모든 것을 간단하게 수행 할 수 있으며, :: operator []의 부작용으로 인해 많은 불편을 겪게되면 추상화하는 것이 좋습니다.

+0

이것이 질문에 어떻게 대답하는지 모르겠습니다. 이것이'std :: map :: operator []'와 어떻게 관련되는지 자세히 설명해 주시겠습니까? – SingleNegationElimination

+0

대부분 제 3 자 라이브러리가 절대 완벽하지는 않다는 것을 깨닫게하려고합니다. "무엇이 더 좋을까요?"라고 대답하면 대답은 "특정 애플리케이션의 요구에 정확히 맞는 것입니다. 추상화하여 필요한대로 작동하게하십시오." – riwalk

1

"읽기 의미 체계에서 []을이 컨테이너 유형에 대한 의미 체계 작성으로 바꾸는 좋은 이유는 무엇입니까?"

조금 더 오래 생각하면 두 가지 이유가있을 수 있습니다. 첫 번째 이유는 효율성입니다. 실제 알고리즘과 의미가 삶을 더 쉽고 어렵게 만들지 여부를 반영하는 데 도움이됩니다. 현재 의미론이 빛나는 알고리즘 중 하나는 키와 관련된 값을 누적하는 것입니다. 당신은 당신이 원하는 될 가능성이 0으로 초기화되는 계수의 각 값에 의존 할 수 있기 때문에

void f(std::vector<std::pair<std::string, double> > const& v) 
{ 
     std::map<std::string, double> count; 

     for (size_t i = 0, sz = v.size(); i < sz; ++i) { 
       count[v[i].first] += v[i].second; 
     } 
} 

지도의 의미는이 경우에 좋은입니다.이 경우 각 키 및 값 쌍에 대해 하나의 검색 만 맵에 적용됩니다. 당신이 비교하면

그 (당신이 제안으로 키가없는 경우 예외를 throw) 파이썬은, 당신이 얻을으로 보이는 지저분하고 덜 효율적인 코드와 같은 :

def f(vec): 
     count = {} 
     for (k, v) in vec: 
       if count.has_key(k): 
         count[k] += v 
       else: 
         count[k] = v 

또는 사용하여 약간 깔끔한 버전 GET()와 디폴트 값.

def g(vec): 
     count = {} 
     for (k, v) in vec: 
       count[k] = count.get(k, 0) + v 
     return count 

두 버전 모두에서 사전에 대한 검색은 각 키 및 값 쌍에 대해 수행됩니다. 요구 사항에 따라 심각한 벌칙이 될 수 있습니다. 이 경우 C++ 맵 의미론이 효율적인 코드에 필요합니다.

C++에는 변경 사항을 보호하는 훌륭한 기능을하는 const가 있습니다. 가끔은 const가 대량으로 과소 평가 된 것으로 의심합니다. 귀하의 경우 const를 사용하면 연산자 []를 사용하여지도의 내용을 변경하지 못하게됩니다.

이 동작의 두 번째 좋은 이유는 여러 언어로 된 연관 배열의 동작과 같습니다. Awk 및 Perl과 같은 언어는 수십 년 동안 연관 배열에 대해 동일한 동작을 보였습니다. 이 언어에서오고 있다면, std :: map의 동작은 아마도 매우 직관적 일 것입니다.

+0

'from collections import Counter ' –

+0

하지만 operator [] 이외의 함수 호출에서'단일 조회 '기능을 제공 할 수있어 반 직관적이지 않고 효율성을 제공 할 수 있습니다. –

+0

나는 Awk와 Perl과 같은 언어에서 왔을 때이 행동이 매우 직관적이라는 것을 깨달았 기 때문에 위를 확장했다. 배경이 다른 경우지도 연산자 []가 직관적 인 것처럼 보일 수 없습니다. –

0

STL에 대한 대부분의 디자인 결정을 이해하려면 해당 진화와 특히 SGI STL을 살펴 봐야합니다.

mapPair Associative ContainerUnique Sorted Associative Container의 모델이다.

두 번째 개념은 을 사용한 키 액세스가 의미가없는 set과 공유되기 때문에 중요합니다.

insert (이 값을 대체하지 않음)의 의미는 mapset에 모두 맞게 설계되어 구현을 구성 할 수 있습니다. 요소가 불변 인 set의 경우 대체는 의미가 없습니다.

그것은 대신 약간 더 복잡, 당신은 mapmultimap에 대한 insert에 대한 대안을 것이다 그래야 따라서 operator[]을 도입 할 때보다,이 이상한 의미가 선정되었다 나의 신념이다 :

map.insert(std::make_pair(key, Value())).first->second = value; 

솔직히, 나는 find (std::key_error)과 같이 행동하는 것을 선호 할 것입니다. 이 의미론은 확실히 직관적이지 않습니다.

관련 문제