std :: unique_ptr <T>가 T의 전체 정의를 알아야합니까?


248

헤더에 다음과 같은 코드가 있습니다.

#include <memory>

class Thing;

class MyClass
{
    std::unique_ptr< Thing > my_thing;
};

Thing유형 정의가 포함되지 않은 cpp에이 헤더를 포함 시키면 VS2010-SP1에서 컴파일되지 않습니다.

1> C : \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ include \ memory (2067) : 오류 C2027 : 정의되지 않은 유형 'Thing'사용

교체 std::unique_ptrstd::shared_ptr그것은 컴파일합니다.

따라서 std::unique_ptr전체 정의가 필요한 현재 VS2010 의 구현이며 완전히 구현에 따라 다릅니다.

아니면? std::unique_ptr구현이 순방향 선언으로 만 작동 하지 못하게하는 표준 요구 사항이 있습니까? 에 대한 포인터 만 가져야하므로 이상한 느낌이 들지 Thing않습니까?


20
C ++ 0x 스마트 포인터를 사용하여 완전한 유형이 필요하지 않은 경우에 대한 가장 좋은 설명은 Howard Hinnant의 "불완전한 유형 및 shared_ptr/ unique_ptr" 입니다.
James McNellis

17
포인터 James에게 감사합니다. 나는 그 테이블을 어디에 두 었는지 잊었다! :-)
Howard Hinnant


5
@JamesMcNellis Howard Hinnant 웹 사이트 링크가 다운되었습니다. 다음은 web.archive.org 버전 입니다. 어쨌든, 그는 같은 내용으로 아래에서 완벽하게 대답했습니다 :-)
Ela782

또 다른 좋은 설명은 Scott Meyers의 Effective modern C ++ 항목 22에 나와 있습니다.
Fred Schoen

답변:


328

여기 에서 채택했습니다 .

C ++ 표준 라이브러리의 대부분 템플릿은 완전한 유형으로 인스턴스화해야합니다. 그러나 shared_ptr하고 unique_ptr있는 부분 예외. 모든 멤버가 아닌 일부 멤버는 불완전한 유형으로 인스턴스화 할 수 있습니다. 이에 대한 동기는 스마트 포인터를 사용한 pimpl 과 같은 관용구를 지원 하고 정의되지 않은 동작을 위험에 빠뜨리지 않는 것입니다.

불완전한 유형이 있고 호출 할 때 정의되지 않은 동작이 발생할 수 있습니다 delete.

class A;
A* a = ...;
delete a;

위의 법률 코드입니다. 컴파일됩니다. 컴파일러는 위와 같은 위 코드에 대해 경고를 표시하거나 표시하지 않을 수 있습니다. 실행되면 나쁜 일이 발생합니다. 운이 좋으면 프로그램이 중단됩니다. 그러나 더 가능성이 높은 결과는 프로그램이 ~A()호출되지 않는 한 자동으로 메모리를 누출한다는 것 입니다.

auto_ptr<A>위 예제에서 사용하면 도움이되지 않습니다. 원시 포인터를 사용한 것처럼 여전히 정의되지 않은 동작이 발생합니다.

그럼에도 불구하고 특정 장소에서 불완전한 수업을 이용하는 것이 매우 유용합니다! 이것은 어디 shared_ptrunique_ptr도움이됩니다. 이 스마트 포인터 중 하나를 사용하면 완전한 유형이 필요한 경우를 제외하고 불완전한 유형으로 벗어날 수 있습니다. 그리고 가장 중요한 것은 완전한 유형이 필요한 경우, 그 시점에서 불완전한 유형의 스마트 포인터를 사용하려고하면 컴파일 타임 오류가 발생한다는 것입니다.

더 이상 정의되지 않은 동작 :

코드가 컴파일되면 필요한 모든 곳에서 완전한 유형을 사용했습니다.

class A
{
    class impl;
    std::unique_ptr<impl> ptr_;  // ok!

public:
    A();
    ~A();
    // ...
};

shared_ptrunique_ptr다른 장소에서 완전한 형태를 필요로한다. 동적 삭제 기와 정적 삭제와 관련이있는 이유는 명확하지 않습니다. 정확한 이유는 중요하지 않습니다. 실제로 대부분의 코드에서 완전한 유형이 필요한 위치를 정확히 아는 것은 중요하지 않습니다. 코드 만 작성하면 잘못되면 컴파일러에서 알려줍니다.

그러나 경우에, 여기 당신에게 도움이되는 여러 회원 문서화 테이블입니다 shared_ptrunique_ptr완전성 요구 사항에 대한이. 멤버에 완전한 유형이 필요한 경우 항목에 "C"가 있고 그렇지 않으면 테이블 항목이 "I"로 채워집니다.

Complete type requirements for unique_ptr and shared_ptr

                            unique_ptr       shared_ptr
+------------------------+---------------+---------------+
|          P()           |      I        |      I        |
|  default constructor   |               |               |
+------------------------+---------------+---------------+
|      P(const P&)       |     N/A       |      I        |
|    copy constructor    |               |               |
+------------------------+---------------+---------------+
|         P(P&&)         |      I        |      I        |
|    move constructor    |               |               |
+------------------------+---------------+---------------+
|         ~P()           |      C        |      I        |
|       destructor       |               |               |
+------------------------+---------------+---------------+
|         P(A*)          |      I        |      C        |
+------------------------+---------------+---------------+
|  operator=(const P&)   |     N/A       |      I        |
|    copy assignment     |               |               |
+------------------------+---------------+---------------+
|    operator=(P&&)      |      C        |      I        |
|    move assignment     |               |               |
+------------------------+---------------+---------------+
|        reset()         |      C        |      I        |
+------------------------+---------------+---------------+
|       reset(A*)        |      C        |      C        |
+------------------------+---------------+---------------+

포인터 변환이 필요한 모든 작업에는 unique_ptr및에 대한 완전한 유형이 필요합니다 shared_ptr.

unique_ptr<A>{A*}생성자는 멀리 얻을 수 불완전 A단지 컴파일러에 대한 호출을 설정하는 데 필요하지 않은 경우 ~unique_ptr<A>(). 예를 들어 unique_ptr, 힙 을두면 불완전한 상태로 벗어날 수 있습니다 A. 이 지점에 대한 자세한 내용은 BarryTheHatchet의 답변 here 에서 찾을 수 있습니다 .


3
훌륭한 답변입니다. 내가 할 수 있다면 나는 +5 할 것이다. 나는 다음 프로젝트에서 이것을 다시 언급 할 것이라고 확신합니다.이 프로젝트에서는 스마트 포인터를 최대한 활용하려고합니다.
matthias

4
테이블의 의미를 설명 할 수 있다면 더 많은 사람들에게 도움이 될 것 같습니다
Ghita

8
참고 사항 : 클래스 생성자는 멤버의 소멸자를 참조합니다 (예외가 발생한 경우 소멸자를 호출해야 함). 따라서 unique_ptr의 소멸자는 완전한 유형이 필요하지만 클래스에 사용자 정의 소멸자를 갖는 것만으로는 충분하지 않으며 생성자도 필요합니다.
Johannes Schaub-litb

7
@Mehrdad :이 결정은 C ++ 98에 대한 것이 었습니다. 그러나 결정은 구현 가능성과 사양의 어려움 (즉, 컨테이너의 어떤 부분이 완전한 유형을 요구하거나 요구하지 않는지)에 대한 우려에서 비롯된 것으로 생각합니다. C ++ 98 이후 15 년의 경험을 보유한 오늘날에도이 영역에서 컨테이너 사양을 완화하고 중요한 구현 기술이나 최적화를 위반하지 않도록하는 것은 쉽지 않은 작업입니다. 나는 그것을 할 수 있다고 생각 합니다. 나는 그것이 많은 일이 될 것이라는 것을 안다 . 한 사람이 시도하는 것을 알고 있습니다.
Howard Hinnant

9
그들은을 정의하기 때문에 그렇지, 위의 설명에서 분명이 문제가 누군가를 위해이기 때문에 unique_ptr그냥 클래스의 멤버 변수로 명시 적으로 (헤더 파일) 클래스 선언의 소멸자 (그리고 생성자)를 선언하고 진행 정의 를 컴파일러가 헤더 파일의 생성자 또는 소멸자를 자동으로 인라인하지 못하도록 오류를 유발하는 것을 방지하기 위해 소스 파일에서 (및 소스 파일에서 지정된 클래스의 완전한 선언과 함께 헤더를 넣습니다). stackoverflow.com/a/13414884/368896 또한이를 상기시켜줍니다.
Dan Nissenbaum

42

MyClass의 기본 소멸자를 생성하려면 컴파일러에 Thing 정의가 필요합니다. 소멸자를 명시 적으로 선언하고 (빈) 구현을 CPP 파일로 이동하면 코드가 컴파일되어야합니다.


5
이것이 기본 기능을 사용할 수있는 완벽한 기회라고 생각합니다. MyClass::~MyClass() = default;구현 파일에서 destuctor body가 의도적으로 비워 두는 대신 지워 졌다고 가정하는 누군가가 나중에 실수로 도로 아래로 제거 할 가능성이 적습니다.
Dennis Zickefoose

@Dennis Zickefoose : 불행히도 OP는 VC ++을 사용하고 있으며 VC ++는 아직 defaulted 및 deleted 클래스 멤버를 지원하지 않습니다 .
ildjarn

6
문을 .cpp 파일로 옮기는 방법에 +1 또한 MyClass::~MyClass() = defaultClang의 구현 파일로 옮기지 않는 것 같습니다 . (아직?)
Eonil

또한 최소한 VS 2017에서는 생성자의 구현을 CPP 파일로 이동해야합니다. 예를 들어 다음 답변을 참조하십시오. stackoverflow.com/a/27624369/5124002
jciloa

15

이것은 구현에 의존하지 않습니다. 작동하는 이유 shared_ptr는 런타임에 호출 할 올바른 소멸자를 판별 하기 때문 입니다. 이는 유형 시그니처의 일부가 아닙니다. 그러나 unique_ptr의 소멸자 유형의 일부이므로 컴파일 타임에 알려야합니다.


8

현재 답변이 기본 생성자 (또는 소멸자)에 문제가있는 이유를 정확하게 파악하지 못하지만 cpp에 선언 된 빈 질문은 그렇지 않습니다.

무슨 일이 일어나고 있습니까 :

외부 클래스 (예 : MyClass)에 생성자 또는 소멸자가 없으면 컴파일러가 기본 클래스를 생성합니다. 이것의 문제점은 컴파일러가 기본적으로 비어있는 기본 생성자 / 소멸자를 .hpp 파일에 삽입한다는 것입니다. 이는 기본 생성자 / 소멸자에 대한 코드가 라이브러리의 이진 파일이 아니라 호스트 실행 파일의 이진 파일과 함께 컴파일됨을 의미합니다. 그러나이 정의는 실제로 부분 클래스를 구성 할 수 없습니다. 따라서 링커가 라이브러리의 바이너리에 들어가 생성자 / 소멸자를 얻으려고하면 아무것도 찾지 못하고 오류가 발생합니다. 생성자 / 소멸자 코드가 .cpp에 있으면 라이브러리 바이너리에 링크 가능한 코드가 있습니다.

이것은 unique_ptr 또는 shared_ptr 사용과 아무런 관련이 없으며 다른 답변은 unique_ptr 구현 (VC ++ 2015가 내 컴퓨터에서 잘 작동 함)에 대한 이전 VC ++의 버그를 혼동하는 것으로 보입니다.

따라서 이야기의 교훈은 헤더에 생성자 / 소멸자 정의가 없어야한다는 것입니다. 선언 만 포함 할 수 있습니다. 예를 들어, ~MyClass()=default;hpp에서는 작동하지 않습니다. 컴파일러가 기본 생성자 또는 소멸자를 삽입하도록 허용하면 링커 오류가 발생합니다.

다른 쪽 참고 : cpp 파일에 생성자와 소멸자가 있더라도 여전히이 오류가 발생하면 라이브러리가 제대로 컴파일되지 않았기 때문일 가능성이 큽니다. 예를 들어, VC ++에서 프로젝트 유형을 콘솔에서 라이브러리로 간단히 변경했을 때 VC ++에 _LIB 전 처리기 기호가 추가되지 않아 정확히 동일한 오류 메시지가 생성 되어이 오류가 발생했습니다.


감사합니다! 그것은 엄청나게 모호한 C ++ 단점에 대한 간결한 설명이었습니다. 많은 문제를 해결했습니다.
JPNotADragon

5

완전성을 위해 :

헤더 : Ah

class B; // forward declaration

class A
{
    std::unique_ptr<B> ptr_;  // ok!  
public:
    A();
    ~A();
    // ...
};

소스 A.cpp :

class B {  ...  }; // class definition

A::A() { ... }
A::~A() { ... }

클래스 B의 정의는 생성자, 소멸자 및 B를 암시 적으로 삭제할 수있는 모든 항목에 의해 보여 져야합니다. 생성자에서 예외가 발생하면 unique_ptr이 다시 파괴됩니다.)


1

템플릿 인스턴스화 시점에서 Thing에 대한 완전한 정의가 필요합니다. 이것이 pimpl 관용구가 컴파일되는 정확한 이유입니다.

가능하지 않다면 사람들은 이런 질문을하지 않을 것 입니다.


-2

간단한 대답은 대신 shared_ptr을 사용하는 것입니다.


-7

나도

QList<QSharedPointer<ControllerBase>> controllers;

헤더 만 포함하면 ...

#include <QSharedPointer>

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