항상 스마트 포인터를 사용하는 것이 좋은 습관입니까?


80

나는 스마트 포인터가 원시 포인터보다 훨씬 더 편하다고 생각합니다. 그렇다면 항상 스마트 포인터를 사용 하는 것이 좋은 생각 입니까? (저는 Java 배경에서 왔기 때문에 명시 적 메모리 관리 개념이별로 마음에 들지 않습니다. 따라서 스마트 포인터에 심각한 성능 문제가없는 한 계속해서 고수하고 싶습니다.)

참고 : 저는 Java 배경에서 왔지만 스마트 포인터 구현과 RAII 개념을 잘 이해하고 있습니다. 따라서 답변을 게시 할 때이 지식을 당연한 것으로 받아 들일 수 있습니다. 거의 모든 곳에서 정적 할당을 사용하고 필요할 때만 포인터를 사용합니다. 내 질문은 단순히 : 원시 포인터 대신 스마트 포인터를 항상 사용할 수 있습니까 ???


9
패턴이나 가이드 라인의 사용이 여러 가지 이유로 유용하지 않은 상황이 있기 때문에 "항상"이라는 단어를 사용하는 것은 좋은 관행에 대해 말할 때 결코 좋은 일이 아닙니다. M.
Max

@Neil 그래 난 그게 다야.
Dony Borris

나는 이것을 불쾌하게하지 않는다는 것을 의미하지만, 좋은 책을 얻고 처음부터 시작해야한다는 것은 분명합니다. 귀하의 용어가 잘못되었으며 귀하의 코드가 "비 C ++"인 것 같습니다.
GManNickG

2
여기 빌어 먹을, 아무것도하지만, 짠 C ++ 프로그래머
브루노

답변:


79

몇 번의 편집을 감안할 때 포괄적 인 요약이 유용 할 것이라는 인상을 받았습니다.

1.하지 않을 때

스마트 포인터를 사용해서는 안되는 두 가지 상황이 있습니다.

첫 번째는 C++실제로 클래스를 사용해서는 안되는 똑같은 상황입니다 . IE : 클라이언트에 소스 코드를 제공하지 않는 경우 DLL 경계. 일화를 말해 보자.

두 번째는 훨씬 더 자주 발생합니다. 똑똑한 관리자는 소유권을 의미 합니다. 수명을 관리하지 않고 포인터를 사용하여 기존 리소스를 가리킬 수 있습니다. 예를 들면 다음과 같습니다.

void notowner(const std::string& name)
{
  Class* pointer(0);
  if (name == "cat")
    pointer = getCat();
  else if (name == "dog")
    pointer = getDog();

  if (pointer) doSomething(*pointer);
}

이 예는 제한적입니다. 그러나 포인터는 잘못된 위치 (널 포인터)를 가리킬 수 있다는 점에서 참조와 의미 상 다릅니다. 이 경우 개체의 수명을 관리하고 싶지 않기 때문에 대신 스마트 포인터를 사용하지 않는 것이 좋습니다.

2. 똑똑한 관리자

똑똑한 관리자 클래스를 작성하지 않는 한 키워드 사용하면 delete 뭔가 잘못된 것입니다.

논란의 여지가있는 관점이지만 결함이있는 코드의 예를 너무 많이 검토 한 후 더 이상 기회를 잡지 않습니다. 따라서 작성 new하는 경우 새로 할당 된 메모리에 대한 스마트 관리자가 필요합니다. 그리고 지금 당장 필요합니다.

당신이 프로그래머가 아니라는 의미는 아닙니다! 반대로, 작업을 반복하는 대신 작동하는 것으로 입증 된 코드를 재사용하는 것이 핵심 기술입니다.

이제 진정한 어려움이 시작됩니다. 어떤 똑똑한 관리자입니까?

3. 스마트 포인터

다양한 특성을 가진 다양한 스마트 포인터가 있습니다.

std::auto_ptr일반적으로 피해야하는 건너 뛰기 (복사 의미가 잘못됨).

  • scoped_ptr: 오버 헤드가 없으며 복사하거나 이동할 수 없습니다.
  • unique_ptr: 오버 헤드 없음, 복사 불가, 이동 가능.
  • shared_ptr/ weak_ptr: 약간의 오버 헤드 (참조 카운팅), 복사 가능.

일반적으로 scoped_ptr또는 unique_ptr. 여러 명의 소유자가 필요한 경우 디자인을 변경하십시오. 디자인을 변경할 수없고 실제로 여러 명의 소유자가 필요한 경우를 사용 shared_ptr하지만 weak_ptr중간 어딘가에를 사용하여 끊어야하는 참조주기에주의 하세요.

4. 스마트 컨테이너

많은 스마트 포인터는 복사 할 수 없으므로 STL 컨테이너와의 사용이 다소 손상됩니다.

shared_ptr오버 헤드 에 의존하는 대신 Boost Pointer Container 의 스마트 컨테이너를 사용하십시오 . 클래식 STL 컨테이너의 인터페이스를 에뮬레이트하지만 소유 한 포인터를 저장합니다.

5. 나만의 롤링

자신의 스마트 매니저를 롤링하고 싶을 때가 있습니다. 미리 사용중인 라이브러리의 일부 기능을 놓친 것이 아닌지 확인하십시오.

예외가있는 상황에서 똑똑한 관리자를 작성하는 것은 매우 어렵습니다. 당신은 일반적으로 해당 메모리를 사용할 수있는 가정 할 수 없다 ( new실패 할 수 있습니다) 또는 Copy ConstructorS는이 no throw보증을.

std::bad_alloc예외 를 무시하고 Copy Constructor여러 헬퍼 중 s가 실패하지 않도록 강요하는 것이 다소 허용 될 수 있습니다 . 결국, 그것이 boost::shared_ptrdeleter D템플릿 매개 변수에 대해 수행하는 작업입니다.

그러나 특히 초보자에게는 권장하지 않습니다. 까다로운 문제이며 지금 당장은 버그를 눈치 채지 못할 것입니다.

6. 예

// For the sake of short code, avoid in real code ;)
using namespace boost;

// Example classes
//   Yes, clone returns a raw pointer...
// it puts the burden on the caller as for how to wrap it
//   It is to obey the `Cloneable` concept as described in 
// the Boost Pointer Container library linked above
struct Cloneable
{
  virtual ~Cloneable() {}
  virtual Cloneable* clone() const = 0;
};

struct Derived: Cloneable
{
  virtual Derived* clone() const { new Derived(*this); }
};

void scoped()
{
  scoped_ptr<Cloneable> c(new Derived);
} // memory freed here

// illustration of the moved semantics
unique_ptr<Cloneable> unique()
{
  return unique_ptr<Cloneable>(new Derived);
}

void shared()
{
  shared_ptr<Cloneable> n1(new Derived);
  weak_ptr<Cloneable> w = n1;

  {
    shared_ptr<Cloneable> n2 = n1;          // copy

    n1.reset();

    assert(n1.get() == 0);
    assert(n2.get() != 0);
    assert(!w.expired() && w.get() != 0);
  } // n2 goes out of scope, the memory is released

  assert(w.expired()); // no object any longer
}

void container()
{
  ptr_vector<Cloneable> vec;
  vec.push_back(new Derived);
  vec.push_back(new Derived);

  vec.push_back(
    vec.front().clone()         // Interesting semantic, it is dereferenced!
  );
} // when vec goes out of scope, it clears up everything ;)

4
좋은 대답입니다! :) 나는 Mr Butterworth가 이것으로부터 무언가를 배울 수 있다고 생각합니다.
Dony Borris

2
저는 개인적으로 Neil의 답변 (일반적으로 특히이 답변)을 좋아합니다. 메모리 관리가 얼마나 까다 롭고 라이브러리가 "상대적으로"새로운 것인지를 고려할 때 주제가 더 심층적 인 설명이 필요하다고 생각했습니다 (여기서는 Pointer Container를 생각하고 있습니다 , 2007 년).
Matthieu M.

"스마트 관리자"란 무엇을 의미합니까? 대답의 그 부분은 나에게 의미가 없습니다. 또한의 의미는 std::auto_ptr대부분의 사람들이 기대하는 것과 다르지만 "일반적으로 피한다"는 말은 말도 안된다고 말하면서 코드의 디자인 문제를 피하는 것이 합리적입니다.
Frunsi

1
@frunsi : "일반적으로 피한다"는 말이 말도 안되는 경우 ISO 표준위원회에 빨리 가서 그 이유를 설명해야합니다. 그들은 auto_ptrC ++ 0x에서 더 이상 사용 unique_ptr하지 않고 대신 ;-)를 사용하도록 권장 함으로써 끔찍한 실수를 저 지르려고합니다 . 공정하게 unique_ptr말하면 auto_ptrC ++ 03에서 엄격한 양도 가능한 소유권을 원하는 경우 선택의 여지가 많지 않은 경우 이동 생성 / 할당을 .
Steve Jessop

@Steve : 좋습니다! unique_ptr은 이동 의미 체계를 사용할 수있을 때 이동하는 방법 인 것 같습니다. 그때까지 auto_ptr은 여전히 ​​유용하지만 100 년 정도 후에 제거 될 것입니다.) 내년에 모두 C ++ 0x 코드를 작성하기 시작하면 (그렇 길 바랍니다) 지금 auto_ptr을 피해야하지만 의심 스럽습니다. .. :) 농담입니다. 지금은 피해야합니다.
Frunsi

18

스마트 포인터 명시 적 메모리 관리를 수행하며, 어떻게 수행하는지 이해하지 못하면 C ++로 프로그래밍 할 때 문제가 발생합니다. 그리고 메모리는 그들이 관리하는 유일한 자원이 아님을 기억하십시오.

그러나 질문에 답하려면 솔루션에 대한 첫 번째 근사치로 스마트 포인터를 선호해야하지만 필요할 때 버릴 준비가되어 있어야합니다. 피할 수있는 경우 포인터 (또는 모든 종류) 또는 동적 할당을 사용해서는 안됩니다. 예를 들면 :

string * s1 = new string( "foo" );      // bad
string s2( "bar" );    // good

편집 : 추가 질문에 대답하려면 "원시 포인터 대신 스마트 포인터를 항상 사용할 수 있습니까 ??? 그런 다음 사용할 수 없습니다. (예를 들어) 고유 한 버전의 연산자 new를 구현해야하는 경우 스마트 포인터가 아닌 포인터를 반환합니다.


10
전혀 도움이되지 않는 대답. 나는 이것을 반대 투표하기에 충분한 담당자가 있었으면 좋겠다.
Dony Borris

8
@Dony 답변의 질은 종종 질문의 질을 반영합니다.

12
@Dony 나는 일반적으로 단순히 도움이되지 않는 답변보다는 잘못 투표합니다. 결국 질문자가 깨달음을 얻기 위해 무엇을 배워야하는지 정확히 아는 것은 어려울 수 있습니다.
Philip Potter

4
안타깝게도 Neil의 답변은 종종 겸손한면으로 제공됩니다. 세상이 자신만큼 똑똑하거나 경험이 많지 않기 때문에 그는 자신을 좌절시키는 질문에 대답하지 말아야합니다.

3
@Neil : 댓글에 OP의 역량 수준에서 몇 가지 잽을 찍었습니다. 그래도 오해하지 마십시오. 나는 그것을 위해 모두입니다. 언어 표준을 앞뒤로 5 번 이상 읽지 않은 사람은 "C ++로 프로그래밍 할 때 문제의 세계"에 처해 있다고 생각합니다.

13

일반적으로 포인터가 필요하지 않다면 포인터 (스마트 또는 기타)를 사용해서는 안됩니다. 객체에 대한 포인터 대신 지역 변수, 클래스 멤버, 벡터 요소 및 유사한 항목을 일반 객체로 만드는 것이 좋습니다. (Java에서 왔기 때문에 아마도 모든 것을으로 할당 할 유혹을받을 것입니다 new. 이것은 권장하지 않습니다.)

이 접근 방식 ( " RAII ")은 대부분의 시간 동안 포인터에 대해 걱정하지 않아도됩니다.

포인터를 사용해야하는 경우 상황과 포인터가 정확히 필요한 이유에 따라 다르지만 일반적으로 스마트 포인터를 사용할 수 있습니다. 그것은하지 않을 수 있습니다 항상 최선의 선택이 될 (굵게), 그러나 이것은 특정 상황에 따라 달라집니다.


4
그렇다면 "의존하는"상황은 무엇입니까? 왜 아무도 말하지 않습니까?
P Shved 2010 년

1
몇 가지 상황을 생각할 수 있습니다. 성능 요구 사항으로 인해 공유 포인터가 부적절 boost::scoped_ptr해질 수 있습니다 ( 그 당시에도 여전히 사용할 수 있지만 부스트에 의존하고 싶지 않습니까?)-또는 C API와 인터페이스해야합니다. ,이 경우 원시 포인터가 더 일관성이 있습니다. 배열을 반복해야하는 경우 반복자는 원시 포인터 일 수도 있습니다.
jalf

1
인스턴스를 생성하는 것보다 오래 지속될 수있는 객체를 생성하는 경우 다른 객체 내에 단순히 포함시킬 수는 없습니다.
Ben Voigt

1
@Ben Voigt : 일반적으로 귀하의 예가 유효하지 않습니다. 당신은 반환 할 수 있습니다 auto_ptr, unique_ptr또는 shared_ptr단지 당신이 소유권을 양도 스코프의 원시 포인터를 전달할 것입니다. scoped_ptr유일한 스마트 전송 / 공유 소유 할 수 없습니다 세트의 포인터
데이비드 로드리게스 - dribeas

1
@Ben : boost 공유 포인터에는이 문제를 해결하는 메커니즘이 있습니다.이 질문을 참조하십시오. stackoverflow.com/questions/1403465/… . 기본적으로 객체에 대한 참조 (계산 의미)를 보유하는 공유 포인터를 가질 수 있지만 참조 될 때 다른 포인터 (이 경우 참조 된 객체의 구성원)를 반환합니다.
Evan Teran

9

스마트 포인터를 사용 하지 않는 좋은시기 는 DLL의 인터페이스 경계입니다. 다른 실행 파일이 동일한 컴파일러 / 라이브러리로 빌드되는지 여부는 알 수 없습니다. 시스템의 DLL 호출 규칙은 스마트 포인터가 포함 된 표준 또는 TR1 클래스의 모양을 지정하지 않습니다.

실행 파일 또는 라이브러리 내에서 pointee의 소유권을 나타내려면 스마트 포인터가 평균적으로이를 수행하는 가장 좋은 방법입니다. 따라서 항상 원시보다 선호하는 것이 좋습니다. 실제로 항상 사용할 수 있는지 여부는 다른 문제입니다.

하지 않는 구체적인 예를 들어, 정점은 객체로 표시되고 모서리는 객체 사이의 포인터로 표시되는 일반 그래프의 표현을 작성한다고 가정합니다. 일반적인 스마트 포인터는 도움이되지 않습니다. 그래프는 순환적일 수 있으며 특정 노드가 다른 노드의 메모리 관리를 담당 할 수 없으므로 공유 및 약한 포인터가 충분하지 않습니다. 예를 들어 모든 것을 벡터에 넣고 포인터 대신 인덱스를 사용하거나 모든 것을 데크에 넣고 원시 포인터를 사용할 수 있습니다. 당신은 사용할 수 있습니다shared_ptr 경우 있지만 오버 헤드 외에는 추가되지 않습니다. 또는 마크 스윕 GC를 찾을 수 있습니다.

더 가장자리 경우 : I 함수 포인터 또는 참조에 의해 매개 변수를 사용 참조하는 것을 선호, 그리고에 대한 포인터 또는 참조를 유지하지 않겠다고 약속 보다는을 shared_ptr그들이 반환 이후 아마도 경우, 어쩌면 그들이 참조를 유지 여부를 궁금해 당신을 떠나지 당신은 참조를 수정하고 또 다시 무언가를 깨뜨릴 것입니다. 참조를 유지하지 않는 것은 종종 명시 적으로 문서화되지 않은 것입니다. 그럴 수는 없지만 그렇습니다. 스마트 포인터는 소유권에 대한 내용을 암시하고 혼란 스러울 수 있다는 잘못된 암시를합니다. 따라서 함수가를 사용하는 경우 shared_ptr참조를 유지할 수 있는지 여부를 문서화해야합니다.


6

많은 상황에서 나는 그들이 갈 길이라고 믿습니다 (더 적은 정리 코드, 누출 위험 감소 등). 그러나 약간의 추가 비용이 있습니다. 가능한 한 빨리 (일부 할당과 자유를해야하는 타이트한 루프) 코드를 작성한다면, 아마도 좀 더 빠른 속도를 내기 위해 스마트 포인터를 사용하지 않을 것입니다. 그러나 나는 그것이 대부분의 상황에서 측정 가능한 차이를 만들 것이라고 의심합니다.


6
타이트한 루프에서 리소스를 할당, 사용 및 파괴하는 경우 사실상 오버 헤드가 0 인 scoped_ptr을 사용할 것입니다 (처음에는 좋지 않을 수 있음). 작업에 적합한 종류의 스마트 포인터를 선택하십시오.
방문자

개체를 삭제해야하는 경우 실제 일부 스마트 포인터 성능 저하 (특히이 없다 scoped_ptr)
dribeas - 데이비드 로드리게스

@ 데이비드 : 좋은 지적이긴하지만 ... 그 상황에서 여전히 하나의 추가 테스트가있을 것이라고 생각했지만 (아마도 무지해서). 나는 어셈블리를 버리고 나 자신을 가르쳐야 할 것입니다.
Mark Wilkins

2
shared_ptr공유 정보 블록의 할당에 오버 헤드가 있고 해당 공유 정보를 기반으로 할당 해제 여부를 확인합니다. 그러나 단일 소유권 스마트 포인터를 사용하면 소멸자는 테스트를 수행 할 필요가 없습니다. 내부 포인터 만 삭제하면됩니다. 예 : libstdc ++ : ~auto_ptr() { delete _M_ptr; }, boost 1.37 : ~scoped_ptr() { checked_delete(ptr); }어디 checked_delete에서 유형 완전성에 대한 컴파일 시간 검사와 delete인라인 될.에 대한 단일 호출이 있습니다.
David Rodríguez-dribeas

@David : 쿨-공유해 주셔서 감사합니다. 나는 뭔가를 배웠다.
Mark Wilkins

4

일반적으로 스마트 포인터를 항상 사용할 수는 없습니다. 예를 들어 스마트 포인터 (예 : Qt)를 사용하지 않는 다른 프레임 워크를 사용하는 경우 원시 포인터도 사용해야합니다.


2

리소스를 처리하는 경우 항상 RAII 기술을 사용해야합니다. shared_ptr 특정 사용 사례에 가장 적합한 스마트 포인터를 선택하십시오. ). 예외가있는 경우 누수를 피하는 유일한 방법입니다.

자원 관리가 포인터를 통해 처리되지 않는 경우에도 원시 포인터가 필요한 경우가 있습니다. 특히 이들은 재설정 가능한 참조를 갖는 유일한 방법입니다. 수명을 명시 적으로 처리 할 수없는 객체 (멤버 속성, 스택의 객체)에 대한 참조를 유지하는 것을 고려하십시오. 그러나 이것은 실제 코드에서 단 한 번만 본 매우 특정한 경우입니다. 대부분의 경우 a를 사용하는 shared_ptr것이 객체를 공유하는 더 나은 방법입니다.


2

스마트 포인터에 대한 내 생각 : 할당 해제가 언제 발생할 수 있는지 알기 어려울 때 좋습니다 (예 : try / catch 블록 내부 또는 현재 함수에서 벗어날 수있는 함수 (또는 생성자!)를 호출하는 함수 내부). 또는 코드의 모든 곳에서 반환되는 함수에 더 나은 메모리 관리를 추가합니다. 또는 컨테이너에 포인터를 넣습니다.

그러나 스마트 포인터에는 프로그램 전체에 대해 지불하고 싶지 않은 비용이 있습니다. 메모리 관리가 손으로 쉽게 할 수 있다면 ( "흠,이 기능이 끝나면이 세 가지 포인터를 삭제해야한다는 것을 알고 있으며이 기능이 완료 될 때까지 실행될 것임을 알고 있습니다."), 컴퓨터가 수행하는주기를 낭비하는 이유는 무엇입니까? 그것?


3
"흠,이 함수가 끝나면이 세 포인터를 삭제해야한다는 것을 알고 있으며이 함수가 완료 될 때까지 실행될 것임을 알고 있습니다."-그게 바로 auto_ptr또는 scoped_ptr. 측정 가능한 오버 헤드를 생성하는 경우는 드뭅니다. 한편 코드를 올바르게 작성하기가 더 쉽습니다. 예를 들어, 세 포인터 중 두 번째 포인터를 획득 할 때 예외가 발생하면 첫 번째 포인터를 해제합니까? 그렇게하려면 스마트 포인터를 사용하는 것과 비교하여 얼마나 많은 코드를 작성해야합니까? 얼마나 자주 당신은 정말 자원을 획득 할 당신이 어디에서 확보해야하지만, 당신의 인수가 실패 할 수 있습니까?
Steve Jessop

그것은 당신을 현재의 함수에서 벗어날 수있는 함수 안에있는 또 다른 좋은 예입니다. 그리고 좋은 점입니다. :)
RyanWilcox

1

예,하지만 스마트 포인터 나 포인터를 사용하지 않고 여러 프로젝트를 진행했습니다. deque, list, map 등과 같은 컨테이너를 사용하는 것이 좋습니다. 또는 가능한 경우 참조를 사용합니다. 포인터를 전달하는 대신 참조 또는 const 참조를 전달하고 거의 항상 참조를 삭제 / 해제하는 것이 비논리적이므로 문제가 발생하지 않습니다 (일반적으로 작성하여 스택에 생성합니다.{ Class class; func(class, ref2, ref3); }


0

그것은. 스마트 포인터는 오래된 Cocoa (Touch) 생태계의 초석 중 하나입니다. 나는 그것이 새로운 것에 계속 영향을 미친다고 믿습니다.

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