동일한 벡터에서 요소를 푸시 백하는 것이 안전합니까?


126
vector<int> v;
v.push_back(1);
v.push_back(v[0]);

두 번째 push_back으로 인해 재 할당이 발생하면 벡터의 첫 번째 정수에 대한 참조가 더 이상 유효하지 않습니다. 안전하지 않습니까?

vector<int> v;
v.push_back(1);
v.reserve(v.size() + 1);
v.push_back(v[0]);

안전한가요?


4
참고 : 현재 표준 제안 포럼에 토론이 있습니다. 그 일부로 누군가가의 구현 예제를 제공했습니다push_back . 또 다른 포스터 에는 버그가 언급되어있어 귀하가 설명하는 사례를 제대로 처리하지 못했습니다. 내가 알 수있는 한, 아무도 이것이 버그가 아니라고 주장하지 않았습니다. 결론을 내릴 수는 없습니다.
Benjamin Lindley

9
죄송하지만 정답에 대한 논란이 여전히 남아 있으므로 어떤 답변을 수락해야할지 모르겠습니다.
닐 커크

4
stackoverflow.com/a/18647445/576911 아래의 다섯 번째 주석 에서이 질문에 대한 의견을 요청 받았습니다 . 나는 현재 말하는 모든 대답을 찬성함으로써 그렇게하고 있습니다 : 예, 같은 벡터에서 요소를 푸시 백하는 것이 안전합니다.
Howard Hinnant

2
@BenVoigt : <shrug> 표준이 말한 내용에 동의하지 않거나 표준에 동의하더라도 명확하게 충분하다고 생각하지 않는 경우, 항상 다음과 같은 옵션을 선택할 수 있습니다. cplusplus.github.io/LWG/ lwg-active.html # submit_issue 이 옵션을 기억할 수있는 횟수보다 더 많이 가져 왔습니다. 때로는 성공적이고 때로는 그렇지 않습니다. 표준의 내용이나 그 내용에 대해 토론하고 싶다면 SO는 효과적인 포럼이 아닙니다. 우리 대화는 규범적인 의미가 없습니다. 그러나 위의 링크를 따라 가면 정상적인 영향을 줄 수 있습니다.
Howard Hinnant

2
@ Polaris878 push_back으로 인해 벡터의 용량에 도달하면 벡터는 더 큰 새 버퍼를 할당하고 이전 데이터를 복사 한 다음 이전 버퍼를 삭제합니다. 그런 다음 새 요소를 삽입합니다. 문제는 새로운 요소가 방금 삭제 된 이전 버퍼의 데이터에 대한 참조입니다. push_back이 삭제하기 전에 값의 사본을 작성하지 않으면 참조가 잘못됩니다.
닐 커크

답변:


31

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#526 은이 문제 (또는 그와 비슷한 것)를 표준의 잠재적 결함으로 처리 한 것처럼 보입니다 .

1) const 참조로 얻은 매개 변수는 함수 실행 중에 변경 될 수 있습니다

예 :

주어진 std :: vector v :

v. 삽입 (v.begin (), v [2]);

v [2]는 벡터의 요소를 움직여 변경할 수 있습니다

제안 된 해결책은 이것이 결함이 아니라는 것입니다.

표준은 작동하지 않을 권한을 부여하지 않으므로 vector :: insert (iter, value)가 작동해야합니다.


17.6.4.9에서 "함수에 대한 인수에 유효하지 않은 값이있는 경우 (예 : 함수 도메인 외부의 값 또는 의도 된 용도에 유효하지 않은 포인터) 동작이 정의되지 않았습니다." 재 할당이 발생하면 모든 반복자와 요소에 대한 참조가 무효화됩니다. 즉, 함수에 전달 된 매개 변수 참조도 유효하지 않습니다.
벤 Voigt

4
요점은 구현이 재 할당을 담당한다는 것입니다. 입력이 처음 정의 된 경우 동작이 정의되도록해야합니다. 스펙은 push_back이 사본을 작성하도록 명확하게 지정하므로 구현시에는 실행 시간을 희생하여 할당 해제 전에 모든 값을 캐시하거나 복사해야합니다. 이 특정 질문에 외부 참조가 남아 있지 않기 때문에 반복자와 참조가 무효화되는지는 중요하지 않습니다.
OlivierD 2009 년

3
@ NeilKirk 나는 이것이 정식 답변이어야한다고 생각합니다. 이것은 본질적으로 동일한 주장을 사용하여 Reddit의 Stephan T. Lavavej 의해 언급되었습니다 .
TemplateRex

v.insert(v.begin(), v[2]);재 할당을 트리거 할 수 없습니다. 그렇다면 이것이 어떻게 질문에 대답합니까?
ThomasMcLeod

@ThomasMcLeod : 예, 분명히 재 할당을 트리거 할 수 있습니다. 새 요소를 삽입하여 벡터 크기를 확장하고 있습니다.
바이올렛 기린

21

예, 안전하며 표준 라이브러리 구현은 후프를 뛰어 넘습니다.

구현 자가이 요구 사항을 어떻게 든 23.2 / 11로 추적한다고 생각하지만 방법을 알 수 없으며 더 구체적인 것을 찾을 수 없습니다. 내가 찾을 수있는 가장 좋은 것은이 기사입니다.

http://www.drdobbs.com/cpp/copying-container-elements-from-the-c-li/240155771

libc ++와 libstdc ++의 구현을 검사하면 그것들도 안전하다는 것을 알 수 있습니다.


9
일부 지원은 실제로 여기에 도움이 될 것입니다.
chris

3
흥미 롭습니다. 나는 그 사건을 고려한 적이 없지만 실제로 달성하기가 매우 어렵다는 것을 인정해야합니다. 그것도 보유하고 vec.insert(vec.end(), vec.begin(), vec.end());있습니까?
Matthieu M.

2
@MatthieuM. 아니오 : 표 100은 "pre : i와 j는 a로 반복자가 아닙니다"라고 말합니다.
Sebastian Redl

2
이것이 나의 기억이기도하므로 지금은 찬성하지만 참조가 필요합니다.
bames53

3
"명시 적으로 또는 다른 함수로 함수를 정의하여 명시하지 않는 한, 컨테이너 멤버 함수를 호출하거나 컨테이너를 라이브러리 함수에 인수로 전달하는 경우 반복자를 무효화하지 않아야합니다. "컨테이너 내의 객체 값을 변경하거나 변경합니다." ? 그러나 vector.push_back그렇지 않으면 지정합니다. "새로운 크기가 이전 용량보다 크면 재 할당이 발생합니다." 그리고 (at reserve) "재 할당은 시퀀스의 요소를 참조하는 모든 참조, 포인터 및 반복자를 무효화합니다."
Ben Voigt

13

이 표준은 첫 번째 예조차도 안전하다는 것을 보장합니다. 인용 C ++ 11

[sequence.reqmts]

3 표 100 및 101에서 ... X는 시퀀스 컨테이너 클래스, 유형의 요소를 포함 a하는 값 , ... 는 lvalue 또는 const rvalue를 나타냅니다.XTtX::value_type

16 표 101 ...

표현 a.push_back(t) 반환 유형 void 운영 의미론 의 사본에 추가 t. 요구를 : T 한다 CopyInsertableX. 컨테이너 basic_string , deque, list,vector

따라서 정확히 사소한 것은 아니지만 구현시를 수행 할 때 참조가 무효화되지 않도록해야합니다 push_back.


7
이것이 이것이 어떻게 안전한지 보지 못합니다.
jrok

4
@ Angew : 그것은 절대 무효화하지 않습니다 t. 유일한 질문은 사본을 만들기 전후에 있습니다. 마지막 문장이 확실하지 않습니다.
Ben Voigt

4
@BenVoigt t나열된 사전 조건을 충족 하므로 설명 된 동작이 보장됩니다. 구현에서는 전제 조건을 무효화 한 다음 지정된대로 동작하지 않도록 변명으로 사용할 수 없습니다.
bames53

8
@BenVoigt 클라이언트는 통화 내내 전제 조건을 유지할 의무가 없습니다. 통화가 시작될 때만 충족되도록합니다.
bames53

6
@BenVoigt 좋은 지적이지만, 전달자가 펑터 for_each가 반복자를 무효화하지 않아야 한다고 생각 합니다. 에 대한 참조를 얻을 수 for_each는 없지만 "op 및 binary_op는 반복자 또는 하위 범위를 무효화하지 않습니다"와 같은 일부 알고리즘 텍스트에서 볼 수 있습니다.
bames53

7

가장 간단한 구현은 push_back필요한 경우 먼저 벡터를 재 할당하고 참조를 복사하는 것이므로 첫 번째 예제가 안전하다는 것은 분명하지 않습니다 .

그러나 적어도 Visual Studio 2010에서는 안전합니다. 구현은 push_back벡터의 요소를 뒤로 밀 때 사례를 특수하게 처리합니다. 코드는 다음과 같이 구성됩니다.

void push_back(const _Ty& _Val)
    {   // insert element at end
    if (_Inside(_STD addressof(_Val)))
        {   // push back an element
                    ...
        }
    else
        {   // push back a non-element
                    ...
        }
    }

8
사양에 이것이 안전해야하는지 알고 싶습니다.
Nawaz

1
표준에 따르면 안전하지 않아도됩니다. 그러나 안전한 방법으로 구현할 수 있습니다.
Ben Voigt

2
나는 그것을 말할 것 @BenVoigt 되어 안전을 위해 필요 (내 대답을 참조).
Angew는 더 이상 SO

2
@BenVoigt 참조를 전달할 때 유효합니다.
Angew는 더 이상 SO

4
@Angew : 충분하지 않습니다. 통화 시간 동안 유효하게 유지되는 참조를 전달해야하지만이 참조는 그렇지 않습니다.
Ben Voigt

3

이것은 표준을 보장하지는 않지만 다른 데이터 포인트로서 LLVM의 libc ++에v.push_back(v[0]) 안전합니다 .

std::vector::push_back__push_back_slow_path메모리를 재 할당해야 할 때 libc ++의 호출 :

void __push_back_slow_path(_Up& __x) {
  allocator_type& __a = this->__alloc();
  __split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), 
                                                  size(), 
                                                  __a);
  // Note that we construct a copy of __x before deallocating
  // the existing storage or moving existing elements.
  __alloc_traits::construct(__a, 
                            _VSTD::__to_raw_pointer(__v.__end_), 
                            _VSTD::forward<_Up>(__x));
  __v.__end_++;
  // Moving existing elements happens here:
  __swap_out_circular_buffer(__v);
  // When __v goes out of scope, __x will be invalid.
}

기존 스토리지 할당을 해제하기 전에뿐만 아니라 기존 요소에서 이동하기 전에 복사해야합니다. 기존 요소의 이동이에서 수행되었다고 가정하면 __swap_out_circular_buffer이 구현은 실제로 안전합니다.
Ben Voigt

@ BenVoigt : 좋은 지적이며 실제로 내부에서 움직이는 것이 정확합니다 __swap_out_circular_buffer. (나는 그것을 언급하기 위해 몇 가지 의견을 추가했습니다.)
Nate Kohl

1

첫 번째 버전은 확실히 안전하지 않습니다 :

표준 라이브러리 컨테이너 또는 문자열 멤버 함수를 호출하여 얻은 반복자 작업은 기본 컨테이너에 액세스 할 수 있지만 수정해서는 안됩니다. [참고 : 특히 반복자를 무효화하는 컨테이너 작업은 해당 컨테이너와 연결된 반복자 작업과 충돌합니다. — 끝 참고]

섹션 17.6.5.9에서


이것은 사람들이 일반적으로 스레딩과 관련하여 생각하는 데이터 레이스에 관한 섹션입니다 ...하지만 실제 정의에는 "이전의 일"관계가 포함 push_back되어 있습니다. 여기서, 참조 무효화는 새로운 테일 요소를 복사 구성하는 것과 관련하여 순서대로 정의되지 않은 것으로 보인다.


1
규칙이 아니라 메모라는 점을 이해해야합니다. 따라서 전술 한 규칙의 결과를 설명하고 있습니다. 결과는 참조와 동일합니다.
Ben Voigt

5
v[0]반복자 의 결과는 마찬가지로 반복자 push_back()가 아닙니다. 따라서 언어 변호사의 관점에서 볼 때 귀하의 주장은 무효입니다. 죄송합니다. 나는 대부분의 반복자가 포인터이며 반복자를 무효화하는 점은 참조의 경우와 거의 동일하지만 인용 한 표준의 일부는 현재 상황과 관련이 없다는 것을 알고 있습니다.
cmaster-monica reinstate

-1. 그것은 완전히 무의미한 인용이며 어쨌든 대답하지 않습니다. 위원회 x.push_back(x[0])는 SAFE 라고 말합니다 .
Nawaz

0

완전히 안전합니다.

두 번째 예에서는

v.reserve(v.size() + 1);

벡터가 크기를 벗어나면을 의미하기 때문에 필요하지 않습니다 reserve.

벡터는 당신이 아닌이 물건에 책임이 있습니다.


-1

push_back은 참조가 아닌 값을 복사하므로 모두 안전합니다. 포인터를 저장하는 경우 벡터에 관한 한 여전히 안전하지만 벡터의 두 요소가 동일한 데이터를 가리키고 있음을 알고 있습니다.

섹션 23.2.1 일반 컨테이너 요구 사항

16
  • a.push_back (t) t의 사본을 추가합니다. 요구 사항 : T는 X에 CopyInsertable이어야합니다.
  • a.push_back (rv) rv의 사본을 추가합니다. 요구 사항 : T는 X로 MoveInsertable이어야합니다.

따라서 push_back을 구현 하면 사본 v[0] 이 삽입 되었는지 확인해야합니다 . 예를 들어, 복사하기 전에 재 할당하는 구현을 가정 할 때 v[0], 사양 의 사본을 추가하거나 위반하는 것은 확실하지 않습니다 .


2
push_back그러나 벡터의 크기조정 하고 순진한 구현에서는 복사가 발생하기 전에 참조를 무효화 합니다. 따라서 표준의 인용으로 이것을 뒷받침 할 수 없다면 나는 그것을 잘못 생각할 것입니다.
Konrad Rudolph

4
"this"는 첫 번째 또는 두 번째 예를 의미합니까? push_back값을 벡터에 복사합니다. 그러나 (내가 볼 수있는 한) 재 할당 후에 발생할 수 있습니다.이 시점에서 복사하려는 참조는 더 이상 유효하지 않습니다.
Mike Seymour

1
push_back참조 로 인수 받습니다 .
bames53

1
@OlivierD : (1) 새로운 공간을 할당 (2) 새로운 요소를 복사 (3) 기존 요소를 이동 구성 (4) 이동 된 요소를 파괴 (5) 기존 스토리지를 해제하는 순서 -첫 번째 버전이 작동하도록합니다.
벤 Voigt

1
@BenVoigt 어쨌든 컨테이너가 그 속성을 완전히 무시하려면 유형이 CopyInsertable이어야하는 이유는 무엇입니까?
OlivierD

-2

23.3.6.5/1부터 : Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid.

우리가 마지막에 삽입하고 있기 때문에, 더 참조는 무효화되지 않습니다 경우 벡터의 크기가 변경되지 않습니다. 따라서 벡터 capacity() > size()가 작동하면 작동이 보장되고 그렇지 않으면 정의되지 않은 동작이 보장됩니다.


나는 사양이 실제로 어느 경우 에나 이것이 작동하도록 보장한다고 믿습니다. 그래도 참조를 기다리고 있습니다.
bames53

문제에는 반복자 또는 반복자 안전에 대한 언급이 없습니다.
OlivierD

3
@OlivierD 반복자 부분은 여기서 불필요합니다 : 나는 references인용 부분에 관심이 있습니다.
Mark B

2
실제로 안전하다는 것이 보장됩니다 (의 대답,의 의미 참조 push_back).
Angew는 더 이상 SO
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.