답변:
다양한 접근 방식을 시도한 결과 오늘 Google C ++ 스타일 가이드 와 일치합니다 .
실제로 포인터 의미가 필요한 경우 scoped_ptr이 좋습니다. STL 컨테이너가 오브젝트를 보유해야하는 경우와 같이 매우 특정한 조건에서만 std :: tr1 :: shared_ptr을 사용해야합니다. auto_ptr을 사용해서는 안됩니다. [...]
일반적으로, 우리는 명확한 객체 소유권을 가진 코드를 디자인하는 것을 선호합니다. 가장 명확한 객체 소유권은 포인터를 전혀 사용하지 않고 객체를 필드 또는 로컬 변수로 직접 사용하여 얻습니다. [..]
권장되지는 않지만 참조 횟수 포인터는 때때로 문제를 해결하는 가장 단순하고 가장 우아한 방법입니다.
또한 "강력한 소유권"이라는 생각의 기차를 따릅니다. 나는 "이 클래스는이 멤버를 소유하고있다"는 것이 적절할 때 명확하게 묘사하고 싶습니다.
나는 거의 사용하지 않습니다 shared_ptr
. weak_ptr
그렇게하면 가능한 한 참조를 늘리지 않고 객체의 핸들처럼 취급 할 수 있도록 최대한 자유롭게 사용 합니다.
나는 scoped_ptr
모든 곳에서 사용 합니다. 명백한 소유권을 보여줍니다. 내가 멤버와 같은 객체를 만들지 않는 유일한 이유는 scoped_ptr에있는 경우 객체를 전달할 수 있기 때문입니다.
객체 목록이 필요하면을 사용 ptr_vector
합니다. 보다 효율적이며 사용하는 것보다 부작용이 적습니다 vector<shared_ptr>
. 나는 당신이 ptr_vector에 타입을 선언하지 못할 수도 있다고 생각하지만 (그것은 오래되었습니다), 그것의 의미는 내 의견으로는 가치가 있습니다. 기본적으로 목록에서 개체를 제거하면 자동으로 삭제됩니다. 이것은 또한 명백한 소유권을 보여줍니다.
무언가에 대한 참조가 필요한 경우, 알몸 포인터 대신 참조로 만들려고합니다. 때때로 이것은 실용적이지 않습니다 (즉, 객체가 구성된 후 참조가 필요할 때마다). 어느 쪽이든, 참조는 분명히 객체를 소유하지 않는다는 것을 보여 주며, 다른 곳에서 공유 포인터 의미를 따르는 경우 일반적으로 알몸 포인터는 추가 혼란을 일으키지 않습니다 (특히 "수동 삭제 없음"규칙을 따르는 경우) .
이 방법을 사용하면 내가 작업 한 iPhone 게임 하나만 delete
호출 할 수 있었고 Obj-C에서 C ++ 브리지로 작성되었습니다.
일반적으로 나는 메모리 관리가 인간에게 맡기기에는 너무 중요하다고 생각합니다. 삭제를 자동화 할 수 있다면해야합니다. shared_ptr의 오버 헤드가 런타임에 너무 비싸면 (스레딩 지원을 끈 경우 등) 동적 할당을 낮추기 위해 다른 것 (예 : 버킷 패턴)을 사용해야합니다.
작업에 적합한 도구를 사용하십시오.
프로그램에서 예외가 발생할 수있는 경우 코드가 예외를 인식하는지 확인하십시오. 스마트 포인터, RAII를 사용하고 2 단계 구성을 피하는 것이 좋은 출발점입니다.
명확한 소유권 의미가없는 순환 참조가있는 경우 가비지 콜렉션 라이브러리 사용 또는 디자인 리팩토링을 고려할 수 있습니다.
좋은 라이브러리를 사용하면 유형이 아닌 개념에 코드를 작성할 수 있으므로 대부분의 경우 리소스 관리 문제 이외의 포인터 유형은 중요하지 않습니다.
다중 스레드 환경에서 작업하는 경우 개체가 여러 스레드에서 공유 될 수 있는지 이해해야합니다. boost :: shared_ptr 또는 std :: tr1 :: shared_ptr 사용을 고려해야하는 주요 이유 중 하나는 스레드 안전 참조 수를 사용하기 때문입니다.
참조 카운트의 개별 할당에 대해 걱정이되는 경우이 문제를 해결하는 방법에는 여러 가지가 있습니다. boost :: shared_ptr 라이브러리를 사용하면 참조 카운터를 풀로 할당하거나 boost :: make_shared (내 환경 설정)를 사용하여 객체와 참조 횟수를 단일 할당으로 할당하여 사람들이 가지고있는 대부분의 캐시 미스 문제를 완화 할 수 있습니다. 객체에 대한 참조를 최상위 수준으로 유지하고 객체에 대한 직접 참조를 전달하여 성능이 중요한 코드에서 참조 횟수를 업데이트 할 때의 성능 저하를 피할 수 있습니다.
공유 소유권이 필요하지만 참조 계산 또는 가비지 콜렉션 비용을 지불하지 않으려는 경우 변경 불가능한 오브젝트 또는 쓰기시 관용구 사용을 고려하십시오.
가장 큰 성능의 승리는 아키텍처 수준, 알고리즘 수준, 그리고 낮은 수준의 관심사는 매우 중요하지만 중요한 문제를 해결 한 후에 만 해결해야한다는 점을 명심하십시오. 캐시 미스 수준에서 성능 문제를 다루는 경우 말 당 포인터와 아무런 관련이없는 허위 공유와 같이 알고 있어야하는 모든 문제가 있습니다.
텍스처 나 모델과 같은 리소스를 공유하기 위해 스마트 포인터를 사용하는 경우 Boost.Flyweight와 같은보다 전문화 된 라이브러리를 고려하십시오.
새로운 표준이 채택되면 이동 의미론, rvalue 참조 및 완벽한 전달 기능을 통해 값 비싼 객체 및 컨테이너 작업을 훨씬 쉽고 효율적으로 수행 할 수 있습니다. 그때까지 auto_ptr 또는 unique_ptr과 같은 파괴적인 복사 의미론을 가진 포인터를 컨테이너에 저장하지 마십시오 (표준 개념). Boost.Pointer 컨테이너 라이브러리를 사용하거나 컨테이너에 공유 소유권 스마트 포인터를 저장하십시오. 성능이 중요한 코드에서는 Boost.Intrusive의 컨테이너와 같은 침입 컨테이너를 선호하여 두 가지를 피하는 것이 좋습니다.
대상 플랫폼이 실제로 결정에 너무 많은 영향을 미치지 않아야합니다. 임베디드 장치, 스마트 폰, 덤폰, PC 및 콘솔은 모두 코드를 올바르게 실행할 수 있습니다. 엄격한 메모리 예산 또는로드 / 애프터로드 후 동적 할당과 같은 프로젝트 요구 사항이보다 유효한 관심사이며 선택에 영향을 미칩니다.
C ++ 0x를 사용하는 경우을 사용하십시오 std::unique_ptr<T>
.
std::shared_ptr<T>
참조 카운트 오버 헤드가있는 것과 달리 성능 오버 헤드가 없습니다 . unique_ptr 은 포인터를 소유하고 있으며 C ++ 0x의 이동 의미론으로 소유권을 전송할 수 있습니다 . 당신은 그들을 복사 할 수 없습니다-단지 이동합니다.
예를 들어 std::vector<std::unique_ptr<T>>
바이너리와 호환 가능하고 성능이 동일한 컨테이너에서도 사용할 수 std::vector<T*>
있지만 요소를 지우거나 벡터를 지우면 메모리가 누출되지 않습니다. 또한 STL 알고리즘과의 호환성이보다 우수 ptr_vector
합니다.
많은 목적을위한 IMO는 이상적인 컨테이너입니다 : 랜덤 액세스, 예외 안전, 메모리 누수 방지, 벡터 재 할당에 대한 낮은 오버 헤드 (장면 뒤의 포인터를 뒤섞음). 많은 목적에 매우 유용합니다.
어떤 클래스가 어떤 포인터를 소유하는지 문서화하는 것이 좋습니다. 가급적이면 일반 객체 만 사용하고 가능한 한 포인터는 사용하지 않는 것이 좋습니다.
그러나 리소스를 추적해야 할 경우 포인터를 전달하는 것이 유일한 옵션입니다. 몇 가지 경우가 있습니다.
지금은 자원 관리 방법을 거의 다룬다 고 생각합니다. shared_ptr과 같은 포인터의 메모리 비용은 일반적으로 일반 포인터의 메모리 비용의 두 배입니다. 이 오버 헤드가 너무 크다고 생각하지는 않지만 리소스가 부족하면 스마트 포인터의 수를 줄이기 위해 게임 디자인을 고려해야합니다. 다른 경우에는 위의 글 머리 기호와 같은 좋은 원칙을 설계하고 프로파일 러가 더 빠른 속도가 필요한 위치를 알려줍니다.
부스트의 포인터를 구체적으로 언급 할 때, 구현이 정확히 필요한 것이 아니라면 피해야한다고 생각합니다. 그들은 처음에 예상했던 것보다 더 큰 비용이 듭니다. 메모리 및 자원 관리에서 중요하고 중요한 부분을 건너 뛸 수있는 인터페이스를 제공합니다.
소프트웨어 개발에 관해서는 데이터에 대해 생각하는 것이 중요하다고 생각합니다. 데이터가 메모리에 어떻게 표현되는지는 매우 중요합니다. 그 이유는 CPU 속도가 메모리 액세스 시간보다 훨씬 빠른 속도로 증가했기 때문입니다. 이것은 종종 메모리 캐시를 가장 현대적인 컴퓨터 게임의 주요 병목 현상으로 만듭니다. 액세스 순서에 따라 데이터를 메모리에 선형으로 정렬하면 캐시에 훨씬 더 친숙합니다. 이러한 종류의 솔루션은 종종 더 깔끔한 디자인, 더 간단한 코드 및 더 쉽게 디버깅하기 쉬운 코드로 이어집니다. 스마트 포인터는 리소스의 동적 메모리 할당을 빈번하게하므로 메모리 전체에 분산됩니다.
이것은 조기 최적화가 아니라 가능한 한 빨리 결정해야하는 건전한 결정입니다. 소프트웨어가 실행될 하드웨어에 대한 아키텍처 이해의 문제이며 중요합니다.
편집 : 공유 포인터의 성능과 관련하여 고려해야 할 몇 가지 사항이 있습니다.
나는 어디에서나 스마트 포인터를 사용하는 경향이 있습니다. 이것이 완전히 좋은 아이디어인지 확실하지 않지만 게으르고 실제 단점을 볼 수 없습니다 (C 스타일 포인터 산술을 원한다면 제외). 나는 그것을 복사 할 수 있다는 것을 알고 있기 때문에 boost :: shared_ptr을 사용합니다. 두 엔티티가 이미지를 공유하면 하나가 죽으면 다른 하나도 이미지를 잃지 않아야합니다.
이것의 단점은 하나의 객체가 가리키고 소유하는 것을 삭제하지만 다른 객체도 그것을 가리키면 삭제되지 않는다는 것입니다.
나는 늙고, oldskool, 그리고주기 카운터입니다. 내 자신의 작업에서 나는 풀 포인터를 사용하고 런타임에 동적 할당을 사용하지 않습니다 (풀 자체 제외). 모든 것이 풀링되며 소유권이 매우 엄격하고 양도 할 수 없습니다. 실제로 필요한 경우 사용자 지정 작은 블록 할당자를 작성합니다. 게임 중에 모든 수영장이 스스로 지울 수있는 상태가 있는지 확인합니다. 물건이 털이 나면 손잡이로 물건을 감싸서 재배치 할 수는 있지만 오히려 그렇지 않습니다. 컨테이너는 커스텀이며 뼈가 매우 튼튼합니다. 또한 코드를 재사용하지 않습니다.
나는 모든 스마트 포인터와 컨테이너 및 반복자의 미덕에 대해 결코 논쟁하지 않을 것이지만, 나는 매우 빠른 코딩을 할 수있는 것으로 알려져 있습니다. 심장 마비와 영원한 악몽처럼).
물론 프로토 타이핑을하지 않는 한, 일할 때마다 모든 것이 달라집니다.
비록 이것이 이상한 답변은 아니지만 거의 모든 사람에게 적합한 곳은 거의 없습니다.
그러나 개인적인 사례에서 특정 유형의 모든 인스턴스를 중앙 랜덤 액세스 시퀀스 (스레드 안전)에 저장하는 대신 32 비트 인덱스 (상대 주소, 즉)로 작업하는 것이 훨씬 더 유용하다는 것을 알았습니다. 절대 포인터가 아닌.
시작하려면 :
T
메모리에 너무 흩어지지 않도록합니다. 이는 모든 종류의 액세스 패턴에 대한 캐시 미스를 줄이고 노드가 포인터가 아닌 인덱스를 사용하여 서로 연결된 경우 트리와 같은 링크 된 구조를 순회하는 경향이 있습니다.즉, 편의성은 단점이며 유형 안전성입니다. 컨테이너 와 인덱스 T
에 모두 액세스 할 수 없으면 인스턴스에 액세스 할 수 없습니다 . 그리고 평범한 구식 은 그것이 어떤 데이터 유형을 참조하는지 전혀 알려주지 않으므로 유형 안전이 없습니다. 에 대한 색인을 사용하여 실수로에 액세스하려고 시도 할 수 있습니다 . 두 번째 문제를 완화하기 위해 종종 이런 종류의 일을합니다.int32_t
Bar
Foo
struct FooIndex
{
int32_t index;
};
어리석은 것처럼 보이지만 사람들이 실수 로 컴파일러 오류없이 Bar
인덱스를 통해 액세스하려고 시도 할 수 없도록 형식 안전성을 다시 제공 Foo
합니다. 편의상, 나는 약간의 불편을 받아들입니다.
사람들에게 큰 불편을 줄 수있는 또 다른 것은 OOP 스타일 상속 기반 다형성을 사용할 수 없다는 것입니다. 크기와 정렬 요구 사항이 다른 모든 종류의 하위 유형을 가리킬 수있는 기본 포인터가 필요하기 때문입니다. 그러나 요즘에는 상속을 많이 사용하지 않습니다 .ECS 접근 방식을 선호하십시오.
에 관해서는 shared_ptr
너무 많이 사용하지 않으려 고합니다. 대부분의 경우 소유권을 공유하는 것이 이치에 맞으며, 그렇게하면 논리적으로 유출 될 수 있습니다. 종종 적어도 높은 수준에서 한 가지는 한 가지에 속하는 경향이 있습니다. shared_ptr
스레드를 완성하기 전에 객체가 파괴되지 않도록 스레드의 로컬 함수처럼 실제로 소유권을 다루지 않는 곳에서 객체의 수명을 연장하는 것이 종종 사용 되는 유혹을 느꼈 습니다. 그것을 사용합니다.
이 문제를 해결하기 위해 shared_ptr
GC 또는 이와 유사한 것을 사용하는 대신 스레드 풀에서 실행되는 수명이 짧은 작업을 선호하고 스레드가 객체를 삭제하도록 요청하면 실제 파괴가 안전한 것으로 지연됩니다 시스템이 스레드가 해당 오브젝트 유형에 액세스 할 필요가 없음을 보장 할 수있는 시간.
나는 때때로 ref-counting을 사용하지만 결국 최후의 수단 전략처럼 취급합니다. 그리고 지속적인 데이터 구조의 구현과 같이 소유권을 공유하는 것이 합리적 일 수있는 몇 가지 경우가 shared_ptr
있습니다.
어쨌든 나는 주로 인덱스를 사용하고 원시 포인터와 스마트 포인터를 거의 사용하지 않습니다. 나는 물체가 연속적으로 저장되고 메모리 공간에 흩어져 있지 않다는 것을 알 때 색인과 문이 열리는 것을 좋아합니다.