전체 객체 또는 컨테이너의 객체에 대한 포인터를 저장해야합니까?


162

처음부터 새로운 시스템을 설계합니다. STL을 사용하여 특정 장기 객체의 목록과 맵을 저장합니다.

질문 : 객체에 복사 생성자가 있는지 확인하고 STL 컨테이너 내에 객체의 사본을 저장해야합니까, 아니면 일반적으로 수명 및 범위를 직접 관리하고 해당 객체에 대한 포인터를 STL 컨테이너에 저장하는 것이 더 낫습니까?

나는 이것이 세부 사항에 대해서는 다소 짧다는 것을 알고 있지만, 존재하는 경우 "이론적"더 나은 대답을 찾고 있습니다.이 두 가지 솔루션이 모두 가능하다는 것을 알고 있기 때문입니다.

포인터를 가지고 노는 것의 두 가지 명백한 단점은 다음과 같습니다. 1) STL 이외의 범위에서 이러한 객체의 할당 / 할당 해제를 직접 관리해야합니다. 2) 스택에 임시 객체를 만들어 컨테이너에 추가 할 수 없습니다.

내가 놓친 다른 것이 있습니까?


36
신이 사이트를 사랑, 이것은 내가 오늘 생각하고 정확한 질문입니다 ... 나를 위해 그것을 요구하는 작업을 해 주셔서 감사합니다 :-)
eviljack

2
또 다른 흥미로운 점은 포인터가 실제로 컬렉션에 추가되었는지 확인하고 그렇지 않으면 메모리 누수를 피하기 위해 delete를 호출하지 않을 것입니다 ... if ((set.insert (pointer)). second = false) {delete pointer;}
javapowered

답변:


68

사람들이 포인터를 사용하는 효율성에 주목하고 있기 때문입니다.

std :: vector의 사용을 고려하고 있고 업데이트가 적고 컬렉션을 반복하는 경우가 많고 다형성이 아닌 유형의 객체 인 경우 "복사본"개체는 더 효율적으로 참조 할 수 있기 때문에 더 효율적입니다.

Otoh, 업데이트가 일반적인 경우 포인터를 저장하면 복사 / 이전 비용이 절약됩니다.


7
캐시 위치의 관점에서, 포인터를 벡터에 저장하는 것은 포인트에 대한 사용자 지정 할당 자와 함께 사용하면 효율적일 수 있습니다. 사용자 지정 할당자는 예를 들어 new 배치를 사용하여 캐시 위치를 관리해야합니다 ( en.wikipedia.org/wiki/Placement_syntax#Custom_allocators 참조 ).
amit

47

이것은 실제로 상황에 따라 다릅니다.

객체가 작고 객체의 사본을 작성하는 것이 경량 인 경우 stl 컨테이너에 데이터를 저장하는 것이 간단하고 평생 관리에 대해 걱정할 필요가 없기 때문에 내 의견으로는 관리하기가 더 쉽습니다.

객체가 크고 기본 생성자가 의미가 없거나 객체의 사본이 비싸면 포인터로 저장하는 것이 좋습니다.

객체에 대한 포인터를 사용하기로 결정한 경우 Boost Pointer Container Library를 살펴보십시오. . 이 부스트 라이브러리는 동적으로 할당 된 객체와 함께 사용할 수 있도록 모든 STL 컨테이너를 래핑합니다.

각 포인터 컨테이너 (예 : ptr_vector)는 컨테이너에 추가 될 때 객체의 소유권을 가져와 해당 객체의 수명을 관리합니다. 또한 참조로 ptr_ 컨테이너의 모든 요소에 액세스합니다. 이것은 당신이 같은 일을 할 수 있습니다

class BigExpensive { ... }

// create a pointer vector
ptr_vector<BigExpensive> bigVector;
bigVector.push_back( new BigExpensive( "Lexus", 57700 ) );
bigVector.push_back( new BigExpensive( "House", 15000000 );

// get a reference to the first element
MyClass& expensiveItem = bigList[0];
expensiveItem.sell();

이 클래스는 STL 컨테이너를 감싸고 모든 STL 알고리즘과 작동하므로 매우 편리합니다.

컨테이너의 포인터 소유권을 호출자에게 전달하는 기능도 있습니다 (대부분의 컨테이너에서 해제 기능을 통해).


38

polymporhic 객체를 저장하는 경우 항상 기본 클래스 포인터 컬렉션을 사용해야합니다.

즉, 컬렉션에 다른 파생 형식을 저장하려는 경우 포인터를 저장하거나 슬라이싱 데몬에서 먹어야합니다.


1
나는 슬라이싱 데몬을 좋아했다!
idichekop 2016 년

22

행사 후 3 년 만에 점프해서 죄송하지만 여기에주의 할 점은 ...

마지막 큰 프로젝트에서 중앙 데이터 구조는 매우 간단한 개체 집합이었습니다. 프로젝트가 시작된 지 약 1 년이지나면서 요구 사항이 발전함에 따라 객체가 실제로 다형성이어야한다는 것을 깨달았습니다. 데이터 구조를 기본 클래스 포인터 세트로 수정하고 객체 저장, 캐스팅 등의 모든 부수적 손상을 처리하는 데 몇 주 동안 어렵고 불쾌한 뇌 수술이 필요했습니다. 새로운 코드가 작동하고 있음을 확신하기까지 몇 달이 걸렸습니다. 덧붙여서, 이것은 잘 설계된 C ++의 객체 모델이 얼마나 어려운지에 대해 생각하게 만들었습니다.

현재 대규모 프로젝트에서 중앙 데이터 구조는 매우 간단한 개체 집합입니다. 프로젝트가 시작된 지 약 1 년 (오늘날 발생), 나는 그 물체가 실제로 다형성이어야한다는 것을 깨달았습니다. 인터넷으로 돌아가서이 스레드를 찾았으며 Boost 포인터 컨테이너 라이브러리에 대한 Nick의 링크를 찾았습니다. 이것이 바로 모든 것을 고치기 위해 지난 번에 써야했던 것이므로 이번에는 둘러 보겠습니다.

어쨌든, 도덕적 인 견해 : 당신의 사양이 100 % 돌로 주조되지 않은 경우 포인터를 찾으면 나중에 많은 작업을 저축 할 수 있습니다.


사양은 결코 석재로 설정되지 않습니다. Boost 포인터 컨테이너가 해당 옵션을 훨씬 더 매력적으로 보이게하지만 포인터 컨테이너를 독점적으로 사용해야한다는 것을 의미하지는 않습니다. 객체 컨테이너를 포인터 컨테이너로 변환해야한다고 결정하면 전체 프로그램을 한 번에 전체적으로 검사해야한다고 회의적입니다. 일부 디자인에서는 이러한 경우가있을 수 있습니다. 이 경우 깨지기 쉬운 디자인입니다. 이 경우 객체 컨테이너의 "약점"에 대한 문제를 비난하지 마십시오.
allyourcode

값 의미론을 가진 벡터에 항목을 남겨두고 내부에서 다형성 동작을 수행 할 수 있습니다.
Billy ONeal

19

두 세계의 장점을 모두 활용 해보십시오. 스마트 포인터 컨테이너 (예 : boost::shared_ptr또는 std::shared_ptr)를 사용하십시오. 메모리를 관리 할 필요가 없으며 대용량 복사 작업을 처리 할 필요가 없습니다.


이 접근법은 Nick Haddad가 Boost Pointer Container Library를 사용하여 제안한 것과 어떻게 다릅니 까?
Thorsten Schöning

10
@ ThorstenSchöning std :: shared_ptr은 부스트에 대한 종속성을 추가하지 않습니다.
James Johnston

공유 포인터를 사용하여 다형성을 처리 할 수 ​​없으므로 포인터를 명시 적으로 캐스팅하지 않는 한이 방법으로이 기능을 놓치게됩니다.
auserdude

11

일반적으로 STL 컨테이너에 객체를 직접 저장하는 것이 가장 간단하고 효율적이며 객체를 사용하기에 가장 편리하므로 가장 좋습니다.

객체 자체에 복사 할 수없는 구문이 있거나 추상 기본 유형 인 경우 포인터를 저장해야합니다 (가장 쉬운 것은 shared_ptr을 사용하는 것입니다)


4
객체가 크고 요소를 자주 이동하는 경우 가장 효율적이지 않습니다.
allyourcode

3

당신은 그 차이를 잘 이해하고있는 것 같습니다. 물체가 작고 복사하기 쉬운 경우에는 반드시 보관하십시오.

그렇지 않다면 힙에 할당 한 스마트 포인터 (auto_ptr, 참조 카운트 스마트 포인터 아님)를 저장하는 것에 대해 생각할 것입니다. 분명히, 스마트 포인터를 선택하면 (임시대로) 임시 스택 할당 객체를 저장할 수 없습니다.

@ Torbjörn 은 슬라이싱에 대해 좋은 지적을합니다.


1
아, 그리고 결코 이제까지 이제까지 auto_ptr은의 모음 생성하지
토브 Gyllebring

auto_ptr은 스마트 포인터가 아니며 참조 횟수를 세지 않습니다.
Lou Franco

auto_ptr에는 비파괴 복사 의미도 없습니다. 에서 auto_ptr은 할당의 행위 one로는 another로부터 참조 발표 할 예정 one변화를 one.
Andy Finkenstadt


2

객체가 코드의 다른 곳에서 참조되는 경우 boost :: shared_ptr의 벡터에 저장하십시오. 이렇게하면 벡터 크기를 조정하더라도 객체에 대한 포인터가 유효한 상태로 유지됩니다.

즉 :

std::vector<boost::shared_ptr<protocol> > protocols;
...
connection c(protocols[0].get()); // pointer to protocol stays valid even if resized

다른 사람이 객체에 대한 포인터를 저장하지 않거나 목록이 커지거나 줄어들지 않으면 일반 객체로 저장하십시오.

std::vector<protocol> protocols;
connection c(protocols[0]); // value-semantics, takes a copy of the protocol

1

이 질문은 잠시 동안 나를 괴롭 혔습니다.

포인터 저장에 의존하지만 적용 할 수없는 몇 가지 추가 요구 사항 (SWIG lua wrapper)이 있습니다.

이 게시물에서 가장 중요한 점은 객체를 사용 하여 직접 테스트하는 것입니다

오늘이 작업을 수행하여 500 만 개의 개체 컬렉션에서 멤버 함수를 호출하는 속도를 테스트했습니다.

이 함수는 xdir 및 ydir (모든 float 멤버 변수)을 기반으로 x 및 y를 업데이트합니다.

두 유형의 객체를 모두 보유하기 위해 std :: list를 사용했으며 객체를 목록에 저장하는 것이 포인터를 사용하는 것보다 약간 빠릅니다. 반면, 성능은 매우 비슷했기 때문에 응용 프로그램에서 어떻게 사용되는지에 달려 있습니다.

참고로 하드웨어에서 -O3을 사용하면 포인터를 완료하는 데 41 초가 걸리고 원시 개체가 완료되는 데 30 초가 걸렸습니다.

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