C ++ 라이브러리 및 프레임 워크가 스마트 포인터를 사용하지 않는 이유는 무엇입니까?


158

필자는 원시 포인터를 거의 사용해서는 안된다는 기사를 몇 개 읽었습니다. 대신 범위 또는 공유 포인터에 관계없이 항상 스마트 포인터 내부에 래핑되어야합니다.

그러나 Qt, wxWidgets 및 Boost와 같은 라이브러리와 같은 프레임 워크는 마치 전혀 사용하지 않는 것처럼 스마트 포인터를 반환하거나 기대하지 않습니다. 대신 원시 포인터를 반환하거나 기대합니다. 그 이유가 있습니까? 공개 API를 작성할 때 스마트 포인터를 멀리해야하나요? 그 이유는 무엇인가요?

많은 주요 프로젝트에서 스마트 포인터를 피하는 것처럼 보일 때 왜 스마트 포인터가 권장되는지 궁금합니다.


23
방금 명명 한 모든 라이브러리는 수년 전에 시작되었습니다. 스마트 포인터는 C ++ 11에서만 진정한 표준이되었습니다.
chrisaycock

22
스마트 포인터에는 예를 들어 임베디드 / 실시간 시스템에서 중요한 오버 헤드 (참조 카운팅 등)가 있습니다. IMHO-스마트 포인터는 게으른 프로그래머를위한 것입니다. 또한 많은 API가 가장 낮은 공통 분모로 이동합니다. 타이핑하면서 발 주위에 불길을 핥는 느낌!
Ed Heal

95
@EdHeal : 발 주위를 핥는 불길을 느낄 수있는 이유는 모든면에서 완전히 틀 렸기 때문입니다. 예를 들어, 어떤 오버 헤드가 unique_ptr있습니까? 전혀 없습니다. Qt / WxWidget은 임베디드 또는 실시간 시스템을 대상으로합니까? 아니요, 데스크톱의 Windows / Mac / Unix 용입니다. 스마트 포인터는 정확한 정보를 얻고 자하는 프로그래머를위한 것입니다.
Puppy

24
실제로 휴대폰은 Java를 실행하고 있습니다.
R. Martinho Fernandes

12
스마트 포인터는 C ++ 11에서만 진정한 표준입니까? 뭐??? 이러한 것들은 20 년 이상 사용되었습니다.
Kaz

답변:


124

표준 스마트 포인터가 등장하기 전에 많은 라이브러리가 작성되었다는 사실을 제외하고 가장 큰 이유는 표준 C ++ ABI (Application Binary Interface)가 없기 때문일 것입니다.

헤더 전용 라이브러리를 작성하는 경우 스마트 포인터와 표준 컨테이너를 마음대로 전달할 수 있습니다. 그들의 소스는 컴파일 타임에 라이브러리에서 사용할 수 있으므로 구현이 아닌 인터페이스의 안정성에만 의존합니다.

그러나 표준 ABI가 없기 때문에 일반적으로 이러한 개체를 모듈 경계를 넘어 안전하게 전달할 수 없습니다 . GCC shared_ptr는 MSVC shared_ptr와 다를 수 있으며 Intel과 다를 수도 있습니다 shared_ptr. 동일한 컴파일러를 사용 하더라도 이러한 클래스는 버전간에 바이너리 호환이 보장되지 않습니다.

결론은 라이브러리 의 미리 빌드 된 버전 을 배포 하려면 의존 할 표준 ABI가 필요하다는 것입니다. C에는 없지만 컴파일러 공급 업체는 주어진 플랫폼에 대한 C 라이브러리 간의 상호 운용성에 대해 매우 우수합니다. 사실상 표준이 있습니다.

상황은 C ++만큼 좋지 않습니다. 개별 컴파일러는 자체 바이너리 간의 상호 운용을 처리 할 수 ​​있으므로 지원되는 모든 컴파일러 (종종 GCC 및 MSVC)에 대한 버전을 배포 할 수 있습니다. 그러나이를 고려하면 대부분의 라이브러리는 C 인터페이스 만 내 보냅니다. 즉, 원시 포인터를 의미합니다.

그러나 비 라이브러리 코드는 일반적으로 원시보다 스마트 포인터를 선호해야합니다.


17
동의합니다. std :: string을 전달하는 것도 고통 스러울 수 있습니다. 이것은 "라이브러리를위한 훌륭한 언어"로서 C ++에 대해 많은 것을 말해줍니다.
Ha11owed

8
결론은 다음과 같습니다. 사전 빌드 버전을 배포하려면 지원하려는 모든 컴파일러에 대해 배포해야합니다.
josefx

6
@josefx : 예 이것은 슬프지만 사실입니다. 유일한 대안은 COM 또는 원시 C 인터페이스입니다. 저는 C ++ 커뮤니티가 이런 종류의 문제에 대해 걱정하기 시작했으면합니다. C ++가 2 년 전의 새로운 언어 인 것과는 다릅니다.
Robot Mess

3
이것이 잘못 되었기 때문에 나는 반대표를 던졌습니다. ABI 문제는 대부분의 경우 관리 할 수있는 것 이상입니다. 사용자 친화적이지는 않지만 ABI는 극복 할 수 없습니다.
Puppy

4
@NathanAdams : 이러한 소프트웨어는 의심 할 여지없이 인상적이고 유용합니다. 그러나 그것은 더 깊은 문제의 증상을 다룹니다. C ++의 수명과 소유권의 의미는 빈곤 한 것과 존재하지 않는 것 사이에 있습니다. 언어가 허용하지 않았다면 이러한 힙 버그는 발생하지 않았을 것입니다. 따라서 스마트 포인터는 만병 통치약이 아닙니다. 처음에 C ++를 사용하여 발생한 손실의 일부를 되찾기위한 시도입니다.
Jon Purdy

40

여러 가지 이유가있을 수 있습니다. 몇 가지를 나열하려면 다음을 수행하십시오.

  1. 스마트 포인터는 최근에 표준의 일부가되었습니다. 그때까지 그들은 다른 도서관의 일부였습니다
  2. 주요 용도는 메모리 누수를 방지하는 것입니다. 많은 라이브러리에는 자체 메모리 관리 기능이 없습니다. 일반적으로 유틸리티와 API를 제공합니다.
  3. 그것들은 실제로 포인터가 아니라 객체이기 때문에 래퍼로 ​​구현됩니다. 원시 포인터에 비해 추가 시간 / 공간 비용이 있습니다. 도서관 사용자는 이러한 오버 헤드를 원하지 않을 수 있습니다.

편집 : 스마트 포인터 사용은 완전히 개발자의 선택입니다. 다양한 요인에 따라 다릅니다.

  1. 성능이 중요한 시스템에서는 오버 헤드를 생성하는 스마트 포인터를 사용하지 않을 수 있습니다.

  2. 이전 버전과의 호환성이 필요한 프로젝트, C ++ 11 특정 기능이있는 스마트 포인터를 사용하고 싶지 않을 수 있습니다.

Edit2 아래 통과로 인해 24 시간 동안 여러 개의 반대표가 있습니다. 아래는 추가 제안 일 뿐이며 답변이 아닌데도 왜 답변이 비추천인지 이해하지 못합니다.
그러나 C ++는 항상 옵션을 열 수 있도록합니다. :) 예

template<typename T>
struct Pointer {
#ifdef <Cpp11>
  typedef std::unique_ptr<T> type;
#else
  typedef T* type;
#endif
};

그리고 코드에서 다음과 같이 사용하십시오.

Pointer<int>::type p;

스마트 포인터와 원시 포인터가 다르다고 말하는 사람들에게는 동의합니다. 위의 코드는 단지 a와 교환 할 수있는 코드를 작성할 수 있는 아이디어 일뿐입니다. #define이것은 강제 가 아닙니다 .

예를 들어 T*는 명시 적으로 삭제해야하지만 스마트 포인터는 삭제하지 않습니다. 우리는 Destroy()그것을 처리 할 템플릿 을 가질 수 있습니다 .

template<typename T>
void Destroy (T* p)
{
  delete p;
}
template<typename T>
void Destroy (std::unique_ptr<T> p)
{
  // do nothing
}

다음과 같이 사용하십시오.

Destroy(p);

같은 방식으로 원시 포인터의 경우 직접 복사 할 수 있고 스마트 포인터의 경우 특수 작업을 사용할 수 있습니다.

Pointer<X>::type p = new X;
Pointer<X>::type p2(Assign(p));

다음 Assign()과 같습니다.

template<typename T>
T* Assign (T *p)
{
  return p;
}
template<typename T>
... Assign (SmartPointer<T> &p)
{
  // use move sematics or whateve appropriate
}

14
On 3. 일부 스마트 포인터에는 추가 시간 / 공간 비용이 있지만 다른 일부는 그렇지 않습니다. 예 std::auto_ptr를 들어 오랫동안 표준의 일부였습니다 (그리고 std::auto_ptr객체를 생성하는 함수에 대한 반환 유형으로 선호합니다. 다른 곳에서는 거의 쓸모가 없습니다). C ++ 11에서는 std::unique_ptr일반 포인터에 대한 추가 비용이 없습니다.
David Rodríguez-dribeas

4
정확히 ...의 모양 unique_ptr과 사라짐 에 대한 좋은 대칭이 있습니다. auto_ptrC ++ 03을 대상으로하는 코드는 나중에 사용해야하며 C ++ 11을 대상으로하는 코드는 전자를 사용할 수 있습니다. 스마트 포인터는 그렇지 않습니다 shared_ptr . 거부 된 표준에 대한 제안을 포함하여 많은 표준과 표준 이 없습니다 .managed_ptr
David Rodríguez-dribeas

2
@iammilind, 흥미로운 점이지만 재미있는 점은 많은 사람들이 권장하는 것처럼 스마트 포인터를 사용하게되면 결국 주요 라이브러리와 호환되지 않는 코드가 생성된다는 것입니다. 물론 필요에 따라 스마트 포인터를 래핑 / 언 래핑 할 수 있지만 번거롭고 일관되지 않은 코드를 생성 할 수 있습니다 (때로는 스마트 포인터를 다룰 때도 있지만 그렇지 않은 경우도 있음).
laurent

7
스마트 포인터에 "추가적인 시간 / 공간 비용"이 있다는 진술은 다소 오해의 소지가 있습니다. unique_ptr런타임 비용을 제외한 모든 스마트 포인터 unique_ptr는 가장 일반적으로 사용되는 포인터 입니다. 때문에 사용자가 제공하는 코드 샘플도, 오해의 소지 unique_ptrT*완전히 다른 개념입니다. 두 가지를 모두 언급한다는 사실은 type서로 교환 할 수 있다는 인상을줍니다.
void-pointer

12
그런 식으로 typedef를 할 수 없습니다. 이러한 유형은 어떤 식 으로든 동등하지 않습니다. 이와 같이 typedef를 작성하면 문제가 발생합니다.
Alex B

35

스마트 포인터 (C ++ 11 이전)에는 두 가지 문제가 있습니다.

  • 비표준, 따라서 각 라이브러리는 자체적으로 재창조하는 경향이 있습니다 (NIH 신드롬 및 종속성 문제).
  • 잠재적 비용

기본 은 비용이없는 점에서 스마트 포인터는 것입니다 unique_ptr. 불행히도 최근에 등장한 C ++ 11 이동 의미론이 필요합니다. 다른 모든 스마트 포인터는 비용 ( shared_ptr, intrusive_ptr)이 있거나 이상적인 의미 ( auto_ptr) 보다 낮습니다 .

C ++ 11이 다가 오면를 가져 오면 std::unique_ptr마침내 끝났다고 생각하고 싶을 것입니다. 저는 그렇게 낙관적이지 않습니다.

소수의 주요 컴파일러 만이 대부분의 C ++ 11을 최신 버전에서만 구현합니다. QT 및 Boost와 같은 주요 라이브러리가 잠시 동안 C ++ 03과의 호환성을 유지할 것으로 예상 할 수 있으며, 이는 새롭고 반짝이는 스마트 포인터의 광범위한 채택을 다소 배제합니다.


13

스마트 포인터에서 멀리 떨어져서는 안되며, 특히 개체를 전달해야하는 응용 프로그램에서 사용됩니다.

라이브러리는 값을 반환하거나 객체를 채우는 경향이 있습니다. 일반적으로 많은 장소에서 사용해야하는 개체가 없으므로 스마트 포인터를 사용할 필요가 없습니다 (적어도 인터페이스가 아니라 내부적으로 사용할 수 있음).

몇 달 동안 개발 한 후 몇 개의 클래스 (모든 클래스의 3 ~ 5 %)에서만 포인터와 스마트 포인터 만 사용했음을 깨달은 라이브러리를 예로 들어 보겠습니다.

참조의한 변수 전달 은 대부분의 장소에서 충분했습니다. 우리는 null이 될 수있는 객체가있을 때마다 스마트 포인터를 사용했고, 우리가 사용한 라이브러리가 강제 할 때 원시 포인터를 사용했습니다.

편집 (내 평판 때문에 주석을 달 수 없음) : 참조로 변수를 전달하는 것은 매우 유연합니다. 객체가 읽기 전용이되도록하려면 const 참조를 사용할 수 있습니다 (객체를 작성할 수 있도록 몇 가지 불쾌한 캐스트를 수행 할 수 있습니다. ) 그러나 가능한 최대의 보호를 얻을 수 있습니다 (스마트 포인터와 동일). 그러나 나는 객체를 반환하는 것이 훨씬 더 좋다는 데 동의합니다.


나는 당신에게 정확히 동의하지 않지만 대부분의 경우 변수 참조 의 전달을 비난하는 생각의 학교가 있음을 지적 할 것입니다 . 나는 그 학교를 고수한다고 고백합니다. 인수를 수정하지 않는 함수를 선호합니다. 어쨌든 내가 아는 한, C ++의 변수 참조는 그들이 참조하는 객체의 잘못된 처리를 막기 위해 아무것도하지 않습니다. 이것이 스마트 포인터가하려는 일입니다.
thb

2
당신은 그것을 위해 const를 가지고 있습니다 (나는 코멘트 할 수있는 것 같습니다 : D).
Robot Mess 2012

9

Qt는 Java가되기 위해 Standard 라이브러리의 많은 부분을 무의미하게 재발 명했습니다. 지금은 실제로 자체 스마트 포인터가 있다고 생각하지만 일반적으로 디자인의 정점은 아닙니다. 내가 아는 한 wxWidgets는 사용 가능한 스마트 포인터가 작성되기 훨씬 전에 설계되었습니다.

Boost의 경우 적절한 곳에서 스마트 포인터를 사용할 것으로 기대합니다. 좀 더 구체적이어야 할 수도 있습니다.

또한 소유권을 강화하기 위해 스마트 포인터가 존재한다는 사실을 잊지 마십시오. API에 소유권 의미가없는 경우 스마트 포인터를 사용하는 이유는 무엇입니까?


19
Qt는 많은 기능이 사용하려는 플랫폼에 충분히 널리 퍼지기 전에 작성되었습니다. 오랫동안 스마트 포인터를 사용해 왔으며 거의 ​​모든 Q * 클래스에서 리소스를 암시 적으로 공유하는 데 사용합니다.
rubenvb 2013

6
모든 GUI 라이브러리는 불필요하게 바퀴를 재창조합니다. Qt에는 QString, wxWidgets에는 wxString, MFC에는 끔찍한 이름이 CString있습니다. UTF-8 std::string은 GUI 작업의 99 %에 충분하지 않습니까?
Inverse

10
@Inverse QString은 std :: string이 없을 때 생성되었습니다.
MrFox

qt가 생성 된시기와 당시 사용 가능한 스마트 포인터를 확인하십시오.
Dainius 2013 년

3

좋은 질문. 나는 당신이 언급하는 특정 기사를 모르지만 비슷한 것을 때때로 읽었습니다. 내 의심은 그러한 기사의 작성자가 C ++ 스타일 프로그래밍에 대한 편견을 품는 경향이 있다는 것입니다. 작가가 꼭 필요한 경우에만 C ++로 프로그래밍 한 다음 가능한 한 빨리 Java로 돌아 가면 C ++ 사고 방식을 공유하지 않습니다.

동일한 작성자 중 일부 또는 대부분이 가비지 수집 메모리 관리자를 선호한다고 의심하는 사람이 있습니다. 나는 그렇지 않지만 그들과는 다르게 생각합니다.

스마트 포인터는 훌륭하지만 참조 횟수를 유지해야합니다. 참조 횟수를 유지하면 비용 (종종 적당한 비용)이 발생하지만 그럼에도 불구하고 런타임에 비용이 발생합니다. 특히 포인터가 소멸자에 의해 관리되는 경우 베어 포인터를 사용하여 이러한 비용을 절약하는 데 아무런 문제가 없습니다.

C ++의 뛰어난 점 중 하나는 임베디드 시스템 프로그래밍에 대한 지원입니다. 베어 포인터를 사용하는 것도 그 일부입니다.

업데이트 : 한 댓글 작성자가 C ++의 새로운 기능 unique_ptr(TR1 이후 사용 가능)이 참조를 계산하지 않는다는 것을 올바르게 관찰했습니다 . 댓글 작성자는 내가 생각하는 것과는 다른 "스마트 포인터"정의를 가지고 있습니다. 그는 정의에 대해 옳을 수 있습니다.

추가 업데이트 : 아래 댓글 스레드가 밝게 표시됩니다. 모두 읽기를 권장합니다.


2
처음에 임베디드 시스템 프로그래밍은 모든 프로그래밍에서 매우 소수이며 관련성이 없습니다. C ++는 범용 언어입니다. 둘째, shared_ptr참조 횟수를 유지합니다. 참조 횟수를 전혀 유지하지 않는 다른 많은 스마트 포인터 유형이 있습니다. 마지막으로, 언급 된 라이브러리는 여유 자원이 많은 플랫폼을 대상으로합니다. 내가 반대 투표자라는 것은 아니지만 내가 말하는 것은 귀하의 게시물이 잘못되었다는 것입니다.
Puppy

2
@thb-나는 당신의 감정에 동의합니다. DeadMG-임베디드 시스템없이 살아보세요. 예-일부 스마트 포인터에는 오버 헤드가 없지만 일부는 있습니다. OP는 라이브러리를 언급합니다. 예를 들어 Boost에는 임베디드 시스템에서 사용되는 부품이 있지만 스마트 포인터는 특정 응용 프로그램에 적합하지 않을 수 있습니다.
Ed Heal

2
@EdHeal : 임베디드 시스템 없이는 살 수 없습니다! 스마트 포인터는 리소스의 수명을 관리해야하는 모든 상황에 적합합니다.
Puppy

4
shared_ptr오버 헤드가 없습니다. 스레드로부터 안전한 공유 소유권 의미 체계가 필요하지 않은 경우에만 오버 헤드가 있습니다.
R. Martinho Fernandes

1
아니요, shared_ptr은 스레드로부터 안전한 공유 소유권 의미 체계에 필요한 최소한의 오버 헤드를 초과합니다. 특히 refcount를 저장하기위한 목적으로 공유중인 실제 객체와는 별도로 힙 블록을 할당합니다. intrusive_ptr이 더 효율적이지만 (shared_ptr과 같이) 객체에 대한 모든 포인터가 intrusive_ptr이라고 가정합니다. 내 앱 에서처럼 사용자 지정 참조 계산 공유 포인터를 사용하면 intrusive_ptr보다 더 낮은 오버 헤드를 얻을 수 있으며 적어도 하나의 스마트 포인터가 T * 값보다 오래 지속된다는 것을 보장 할 수있을 때마다 T *를 사용할 수 있습니다.
Qwertie 2012

2

다른 유형의 스마트 포인터도 있습니다. 네트워크 복제 (액세스 여부를 감지하고 서버에 대한 수정 사항을 전송하는 것), 변경 내역을 유지하고, 액세스 사실을 표시하여 언제 조사 할 수 있는지에 대한 특수한 스마트 포인터를 원할 수 있습니다. 디스크 등에 데이터를 저장합니다. 포인터에서 그렇게하는 것이 최선의 해결책인지 확실하지 않지만 라이브러리에 내장 된 스마트 포인터 유형을 사용하면 사람들이 그 안에 갇혀 유연성을 잃을 수 있습니다.

사람들은 스마트 포인터 이상의 다양한 메모리 관리 요구 사항과 솔루션을 가질 수 있습니다. 메모리를 직접 관리하고 싶을 수도 있고, 메모리 풀에 공간을 할당하여 런타임이 아닌 미리 할당 할 수도 있습니다 (게임에 유용함). C ++의 가비지 수집 구현을 사용하고있을 수 있습니다 (아직 존재하지 않지만 C ++ 11에서는이를 가능하게 함). 아니면 내가 그들에게 신경을 쓸까 봐 걱정할만큼 고급스러운 일을하지 않을 수도 있고, 초기화되지 않은 객체 등을 잊지 않을 것임을 알 수 있습니다. 포인터 목발없이 기억을 관리하는 능력에 자신이있을 수 있습니다.

C와의 통합도 또 다른 문제입니다.

또 다른 문제는 스마트 포인터가 STL의 일부라는 것입니다. C ++는 STL없이 사용할 수 있도록 설계되었습니다.


" 또 다른 문제는 스마트 포인터가 STL의 일부라는 것입니다. "그렇지 않습니다.
curiousguy

0

그것은 또한 당신이 일하는 도메인에 달려 있습니다. 저는 생계를 위해 게임 엔진을 작성합니다. 우리는 전염병과 같은 부스트를 피합니다. 게임에서 부스트의 오버 헤드는 용납되지 않습니다. 핵심 엔진에서 우리는 우리 자신의 stl 버전 (ea stl과 매우 유사)을 작성했습니다.

양식 애플리케이션을 작성하려면 스마트 포인터 사용을 고려할 수 있습니다. 그러나 일단 메모리 관리가 제 2의 성질이면 메모리를 세밀하게 제어하지 못하는 것은 조용해집니다.


3
"부스트 오버 헤드"와 같은 것은 없습니다.
curiousguy

4
나는 shared_ptr이 내 게임 엔진을 주목할만한 정도로 느리게 한 적이 없습니다. 그들은 생산 및 디버깅 프로세스를 가속화했습니다. 또한 "부스트 오버 헤드"란 정확히 무엇을 의미합니까? 캐스팅하기에 꽤 큰 담요입니다.
derpface

@curiousguy : 모든 헤더와 매크로 + 템플릿 부두의 컴파일 오버 헤드입니다 ...
einpoklum
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.