태그 보관물: c++11

c++11

std :: unique_ptr <T>가 T의 전체 정의를 알아야합니까? include \ memory (2067)

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

#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않습니까?



답변

여기 에서 채택했습니다 .

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 에서 찾을 수 있습니다 .


답변

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


답변

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


답변

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

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

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

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

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

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


답변

완전성을 위해 :

헤더 : 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이 다시 파괴됩니다.)


답변

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

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


답변

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