2009-11-22 4 views
4

포인터를 매개 변수로 허용하는 함수가 있다고 가정하십시오. 이 함수는 std::vector<>::push_back()을 사용하여이 포인터의 수명주기를 관리하므로 예외가 발생할 수 있습니다.괜찮은 표준인가요 :: auto_ptr <> 유스 케이스인가요?

void manage(T *ptr); 

과 같이 호출 : :이처럼 선언하면

manage(new T()); 

std::vector<>에 포인터를 밀어 예외가 발생하는 경우, 내가 효과적으로, 메모리 누수를하지 않은 가지고있다 나는?

void manage(std::auto_ptr<T> ptr); 

내 문제를 해결 :이 같은 기능을 선언

겠습니까?

우선 스택에 std::auto_ptr을 할당하고 (포인터를 통해 소유권을 획득하는 것으로 추측합니다) 예외를 던질 것으로 예상됩니다. 안전한.

그런 다음 함수 내에서 원시 포인터를 std::vector<>으로 푸시합니다.이 작업이 실패하면 포인터가 추가되지 않지만 스마트 포인터는 여전히 포인터를 소유하므로 포인터가 그대로 유지됩니다. 의해 파괴됨. 푸시가 성공하면 해당 포인터를 통해 스마트 포인터의 소유권을 제거하고 반환합니다. 예외를 throw 할 수 없으므로 항상 정상적으로 작동합니다.

내 이론이 맞습니까?

- 편집 - 나는 할 수 없습니다

아니, 나는 생각한다. 그렇게하면 rvalue에 대한 const가 아닌 참조를 취해야합니다 (스마트 포인터에서 소유권을 제거하기 위해). 나는 그 일을하기 위해서

std::auto_ptr<T> ptr(new T()); 
manage(ptr); 

을 쓰는 것이 좋을지도 모르겠다. 나는 코드를 많이 오염시키지 않고 RAII를 구현할 수 있도록이 글을 쓰고있다. 그 일을하는 것은 도움이되지 않습니다.

void manage(T *ptr) 
{ 
    std::auto_ptr<T> autoPtr(ptr); 
    vector.push_back(ptr); 
    autoPtr.release(); 
} 

: 그것은 캐치 (22)

것 - - 편집 2

당기 제이슨 Orendorff이 거기 여기에 독자에 의해 빠른 참조를 위해 말, 최고의 솔루션은 다음과 같은 것 같다 이 r- 수치로 쓸모 const가 아닌 참조의 문제를 해결한다.

내가 코딩을하고있어이 클래스를 완료하면 나는 사람이 유용 발견 한 경우에는 다시 여기에 게시합니다.

-3 편집 -

좋아, 여기에 많은 논의가 있었다, 그리고 내가 전에 명확히해야 핵심 포인트가있다. I에 유래에 게시 할 때 일반적으로, 나는 일반적으로, 즉 완전히 쓸모가, 내 질문 뒤에 이유를 설명하려고합니다. 그래서 이번에는 그 지점으로 곧장 가야한다고 생각했습니다. 그것은 불행하게도 내 머리가 지금 교착되어

XD

아주 잘 작동하지 않았다, 그래서 나는 심지어 내가 처음 내 목표를 달성하기 위해 무슨 생각을 제대로 설명 할 수없는 것이라고 생각 끈다.나는 원자 적 연산과 많은 경우에 맞는 예외 - 안전한 코드 작성을위한 좋은 솔루션을 찾으려고 노력하고있다. 그러나 실제로, 나는 이것을 XD로 처리 할 수 ​​없다. 나는 이것이 시간만으로 마스터 할 것 같은 종류라고 생각한다.

저는 정말 새로운 C++ 프로그래머이고 게임 개발에 중점을 둡니다. 게임 엔진에서 예외가 발생하면 실행이 끝납니다. 시스템은 내 프로세스를위한 모든 메모리를 확보 할 것이므로 하나 또는 두 개의 포인터가 여기저기서 새어 나오는 것이 중요하지 않습니다. 이제 서버 응용 프로그램을 개발 중이므로 예외를 처리하는 것이 서버를 손상시킬 수 있으므로 예외 처리가 어렵습니다. 요청을 "중단해야"합니다.

"글쎄, 클라이언트, 불행히도 개발자는이 조건을 미리 예상하지 못했기 때문에 나중에 다시 시도해야합니다. (여기까지는 게임 엔진과 기본적으로 동일합니다. 다시 말해, 요청은 프로세스의 전체적인 과정이 아닌 요청의 컨텍스트에만 격리되어 있습니다.) 은 모든 것이 유효한 상태 인에 남아 있기 때문에 당황하지 마십시오 (단, 여기에는 차이점이 하나 있습니다). 종료되지 않았으므로 운영 체제에서 자원을 확보 할 수 없으며 사용자의 계정을 완전히 잠그지 않도록 작업을 지금 실행 취소해야합니다. 예를 들어 서버가 제공하는 전체 서비스). "

다음 번에 더 좋은 질문을 쓸 수 있도록 더 많은 코드를 작성하고 내 문제를 기록 할 것입니다. 나는 지금 이것을 묻기 위해 준비하지 않았다, 정말로 유감스럽게 생각한다.

답장을 보내 주셔서 감사합니다. 정말 stackoverflow를 좋아합니다. 내 질문에 얼마나 빨리 답하고 귀하의 답변을 계몽하는 것이 절대적으로 놀라운 일입니다. 감사.

+0

이 방법을 사용하면'manage'에 다소 이상한 인터페이스가 생깁니다. 모든 상황에서 인수를 삭제할 것으로 예상됩니다 (유지하거나 삭제함으로써). 왜 'auto_ptr'을 통해 노출하는 것이 아니라 자체적으로 할당을 처리하지 않습니까? 나는'auto_ptr'을 주로 내부 구현이 아니라 인터페이스라고 봅니다. –

+0

사실, 인터페이스가 불필요하게 이상합니다. Jason Orendorff가 제안한대로 고정 시켰습니다. –

+0

하나의 어려움이 여전히 남아 있습니다 - 실패하면 소유권을 가져 가야하나요? 그것은 RAII와 예외적 인 안전성 때문에'manage'가 실패하면 포인터의 소유권을 얻지 못했고 따라서 호출자가 여전히 그것을 소유하고 있으므로 자유롭게해야한다고 제안합니다. 그것도 복잡해질 수 있습니다. –

답변

1

나는 또 다른 대답을 보증하기에 충분한 토론이 이루어 졌다고 생각한다.

첫째, 실제 질문에 대답하려면 소유권 이전이 발생할 때 스마트 포인터로 인수를 전달하는 것이 절대적으로 (그리고 필요합니다!) 똑같습니다. 스마트 포인터를 전달하는 것은이를 달성하는 일반적인 관용구입니다.

void manage(std::auto_ptr<T> t) { 
    ... 
} 
... 

// The reader of this code clearly sees ownership transfer. 
std::auto_ptr<T> t(new T); 
manage(t); 

이제 모든 스마트 포인터가 명시 적으로 생성자를 왜 아주 좋은 이유가있다. 다음 함수는 (그것이 당신의 공상을 간지러워 경우 정신적 boost::shared_ptrstd::auto_ptr 교체) 고려 : std::auto_ptr는 암시 적 생성자를 가지고 있다면

void func(std::auto_ptr<Widget> w, const Gizmo& g) { 
    ... 
} 

갑자기이 코드를 컴파일합니다 :

func(new Widget(), gizmo); 

그 문제점은 무엇입니까? "효과적인 C++", 항목 17에서 거의 직접 촬영 :

는 C++에서 인수 평가의 순서는 정의되지
func(new Widget(), a_function_that_throws()); 

때문에, 당신은 아주 잘 다음과 같은 순서로 평가 인수를 가질 수 있습니다 new Widget(), a_function_that_throws(), std::auto_ptr 생성자를. 함수가 throw되면 누수가 발생합니다. 따라서 필요 발매 예정 모든 자원이 전에 RAII 클래스에 건설에 포장 할

은 함수에 전달된다. 즉, 모든 스마트 포인터를 함수의 인수로 전달하기 전에 모든 스마트 포인터를 생성해야합니다. 스마트 포인터를 const 참조로 복사 생성 가능하게 만들거나 암시 적으로 구성 가능하게 만드는 것은 안전하지 않은 코드를 장려합니다. 명시 적 구성은보다 안전한 코드를 시행합니다.

이제 왜 이렇게하면 안됩니까?

이미 언급했듯이 인터페이스 관용구는 내가 소유 한 포인터를 전달할 수 있으며 삭제할 수 있음을 알립니다. 따라서 아무 것도 나를 막지 못합니다.

T item; 
manage(&t); 

// or 
manage(&t_class_member); 

물론 재앙입니다. 하지만 "물론 인터페이스가 의미하는 바를 알고 있습니다. 결코 그런 식으로 사용하지 않을 것입니다"라고 말할 것입니다. 그러나 나중에 함수에 추가 인수를 추가 할 수 있습니다. 또는 누군가 (당신 또는 3 년 후)가이 코드를 따라 왔을 때, 당신이하는 것과 같은 방식으로 그것을 보지 못할 수도 있습니다.

  1. 이 가상의 "다른 사람"은 댓글이없는 헤더 만 볼 수 있으며 소유권 이전이 없다고 추정 할 수 있습니다.
  2. 다른 코드에서이 함수를 사용하는 방법을 볼 수 있으며 헤더를 보지 않고 사용법을 복제 할 수 있습니다.
  3. 그들은 코드 자동 완성을 사용하여 함수를 호출하고 주석이나 함수를 읽지 않으며 소유권 이전이 없다고 추정 할 수 있습니다.
  4. 이들은 사용자가 manage 함수를 래핑하는 함수를 작성할 수 있지만 다른 누군가가 래퍼 함수를 ​​사용하고 원래 함수의 문서를 놓치게됩니다.
  5. 모든 이전 코드를 컴파일 (자동이 안전하지 않은) 그래서 그들은 당신에게 코드를 "확장"을 시도 할 수 있습니다

    : 당신이 볼 수 있듯이

    void manage(T *t, const std::string tag = some_function_that_throws()); 
    

는, 스마트 포인터의 명시 적 건설을 만든다 위의 경우 안전하지 않은 코드를 작성하는 것이 훨씬 어렵습니다.

따라서 수십 년에 걸친 C++ 전문 기술에 대해 "좋고"재미있는 API를 만드는 것을 권장하지 않습니다.

내 2c.

4

이렇게 할 수는 있지만 예외가 발생하지 않는 경우에도 정리가 필요합니다. 조금 번거 롭습니다. 당신이 계획대로가는 것들의 경우에 정리를하는 것에 대한 잊을 수 - 당신이 boost::shared_ptr 같은 것을 사용하는 경우

은 (예를 들어이 MS's implementation을 볼 나는이 같은뿐만 아니라 TR1 라이브러리에 생각).

이 작업을 수행하려면 벡터에서 boost::shared_ptr <T> 인스턴스를 수락해야합니다. 그런 다음 원래 인스턴스를 정리하고 모든 인스턴스가 정상적으로 작동하면 벡터 인스턴스를 활성 상태로 유지합니다. 문제가 발생하면 모든 boost::shared_ptr 인스턴스가 파괴되고 누출이 없게됩니다.

스마트 포인터를 사용하면 작업에 적합한 하나를 선택하는 것이 좋으며,이 경우 공유 소유권 (또는 단순 소유권 전송)이 목표이므로 대부분의 플랫폼에서 std :: auto_ptr보다 더 나은 후보가 있습니다.

+0

예외를 throw하지 않는 경우 개체를 "정리"할지 여부를 제어하는 ​​것이 실제로 의도입니다. 구현하려고하는 클래스를 ScopeRollback이라고합니다. 그것은 clear()되기 전에 파괴 될 경우 추가 할 boost :: functions를 실행할 것입니다. 'boost :: shared_ptr'을 사용할 때의 문제점은 바이러스 성입니다. shared_ptr에서 소유권을 제거 할 수는 없지만 실제로 필요합니다. 어쨌든 내 코드는 실제로 잘못되었다고 생각합니다. 자세한 내용은 내 편집을 참조하고 응답 해 주셔서 감사합니다. –

+0

@ n2liquid : 아, 알겠습니다. 그러면 발신자에 대한 정리 요구 사항이 정당화 될 수있는보다 전문화 된 사례 일 수 있습니다. 나는 여전히 부스트 smart_ptr의 도서관을 정독하기를 촉구한다. 왜냐하면 이미 많은 다른 것들이 있기 때문에 당신은 이미 부스트를 사용하고있다. 어쩌면 다른 것이 당신의 필요에 더 잘 어울릴 것인가? – jkp

1

네, 괜찮습니다.(아래에서 편집하십시오.) (jkp는 내가 놓친 것을보고 있을지 모르지만, "예외가 발생했을 때 정리해야합니다"라고 생각하지 않습니다. .

)에 auto_ptr은 당신을 위해 개체를 삭제합니다 케이스 그러나 나는 그것이 호출자에서 auto_ptr은 헛소리 숨기기 위해 더 나은 여전히있을 거라고 생각 :

void manage(T *t) { 
    std::auto_ptr<T> p(t); // to delete t in case push_back throws 
    vec.push_back(t); 
    p.release(); 
} 

편집이 : 내가 처음에 "쓴 예, 그건를 원래 "manage(auto_ptr<T>) 계획을 참고하고 있지만, 시도했지만 작동하지 않는 것으로 나타났습니다. 생성자 auto_ptr<T>::auto_ptr(T *)explicit입니다. 컴파일러는 암시 적으로 해당 포인터를 auto_ptr으로 변환 할 수 없으므로 manage(new T)을 작성할 수 없습니다.manage(T *)은 어쨌든 더 친숙한 인터페이스입니다!

+0

오오! 이런! 내가 어떻게 생각하지 못했을까요? : DD 나는 그것이 저의 편집에서 표현한 문제를 해결한다고 생각합니다. 감사합니다. 어쨌든, jkp는 던져지지 않은 예외 상황에서 정리해야한다고 말했다.) 감사합니다. –

+0

@ Jason : 네, 오타를 만들었습니다. 어쨌든, 당신의 솔루션에 대한 좋은 호소 (이제 나는 상황을 안다). +1. – jkp

+0

사실 나는 동의하지 않습니다. auto_ptr을 숨김으로써 함수 주석 (읽었거나 읽지 않을 수도 있음)에 명시하지 않는 한 객체가 삭제되는지 여부를 호출자에게 알 수 있습니다. auto_ptr을 값으로 전달하는 것은 객체 소유권 이전 의도를 문서화하는 훌륭한 방법입니다.호출자는 실제 포인터 값을 함수에 전달하자마자 실제 포인터 값을 잃어 버리고 정의되지 않은 포인터 대신 NULL 포인터가 남게됩니다. –

4

일반적으로 함수 인수의 유형으로 std::auto_ptr을 사용하면 호출자에게 함수가 객체의 소유권을 가져오고 해당 함수를 삭제할 책임이 있음을 명확하게 알립니다. 함수가 해당 설명에 적합한 경우 다른 이유와 관계없이 항상 auto_ptr을 사용하십시오.

+0

이것은 호출자에게 강제로'auto_ptr' 객체를 생성하도록합니다. 그것은 확실히 드문 일입니다. –

+1

그건 정확히 생각입니다. 호출자가 호출자에게 'auto_ptr'을 생성하도록 강요합니다 (이미 가지고 있지 않은 경우 - heap에 객체를 소유하고 있다면 그 객체가 있어야합니다). 1) 객체가 소유하고 있음을 분명히합니다 소유권이 포기됨을 의미합니다. –

+0

지금 설명을들을 수 있습니다. "새로운 T를 관리 할 수는 없습니다."(표준) :: auto_ptr p (new T); manage (p);'. "왜, 왜?" "그것은 당신 자신의 이익을위한 것입니다. 이제 당신은 소유권이 포기되었음을 압니다." "FML." –

관련 문제