불완전한 유형의 std :: unique_ptr은 컴파일되지 않습니다


202

나는 pimpl-idiom을 std::unique_ptr다음 과 함께 사용하고 있습니다 :

class window {
  window(const rectangle& rect);

private:
  class window_impl; // defined elsewhere
  std::unique_ptr<window_impl> impl_; // won't compile
};

그러나 304 줄에서 불완전한 유형의 사용과 관련하여 컴파일 오류가 발생합니다 <memory>.

sizeof불완전한 유형 ' uixx::window::window_impl' 에 ' '을 (를) 잘못 적용했습니다.

내가 아는 std::unique_ptr한 불완전한 유형으로 사용할 수 있어야합니다. 이것은 libc ++의 버그입니까? 아니면 여기서 잘못하고 있습니까?


완전성 요구 사항에 대한 참조 링크 : stackoverflow.com/a/6089065/576911
하워드 Hinnant

1
pimpl은 종종 그 이후로 구성되고 수정되지 않습니다. 나는 보통 사용 성병 :: shared_ptr의 <const를 window_impl>
mfnx

관련 : MSVC에서 왜 이것이 작동하는지, 작동하지 못하게하는 방법을 알고 싶습니다 (GCC 동료의 편집을 중단하지 않도록).
Len

답변:


258

다음은 std::unique_ptr불완전한 유형의 예입니다 . 문제는 파괴에 있습니다.

와 함께 pimpl을 사용 unique_ptr하는 경우 소멸자를 선언해야합니다.

class foo
{ 
    class impl;
    std::unique_ptr<impl> impl_;

public:
    foo(); // You may need a def. constructor to be defined elsewhere

    ~foo(); // Implement (with {}, or with = default;) where impl is complete
};

그렇지 않으면 컴파일러가 기본 컴파일러를 생성하므로 이에 대한 완전한 선언이 foo::impl필요합니다.

템플릿 생성자가있는 경우 impl_멤버를 생성하지 않아도 문제가 발생합니다 .

template <typename T>
foo::foo(T bar) 
{
    // Here the compiler needs to know how to
    // destroy impl_ in case an exception is
    // thrown !
}

네임 스페이스 범위에서 다음을 사용 unique_ptr하면 작동하지 않습니다.

class impl;
std::unique_ptr<impl> impl_;

컴파일러는이 정적 지속 기간 객체를 삭제하는 방법을 여기에서 알아야합니다. 해결 방법은 다음과 같습니다.

class impl;
struct ptr_impl : std::unique_ptr<impl>
{
    ~ptr_impl(); // Implement (empty body) elsewhere
} impl_;

3
첫 번째 해결책 ( foo 소멸자를 추가하면 )이 클래스 선언 자체를 컴파일 할 수 있지만 해당 유형의 객체를 어디에서나 선언하면 원래 오류가 발생합니다 ( '잘못된 응용 프로그램'sizeof '...').
Jeff Trull

38
주목할만한 훌륭한 답변; 예를 들어 foo::~foo() = default;src 파일 에 배치하여 기본 생성자 / 소멸자를 계속 사용할 수 있습니다
assem

2
템플릿 생성자와 함께 사는 한 가지 방법은 클래스 본문에서 생성자를 선언하지만 정의하지 않고 완전한 impl 정의가 보이는 곳에서 정의하고 필요한 모든 인스턴스화를 명시 적으로 인스턴스화하는 것입니다.
enobayram

2
어떤 경우에는 이것이 어떻게 작동하고 다른 것은 그렇지 않은지 설명 할 수 있습니까? pimpl 관용구를 unique_ptr과 함께 사용하고 소멸자가없는 클래스를 사용했으며 다른 프로젝트에서 코드가 언급 된 오류 OP로 컴파일되지 않습니다.
Curious

1
c ++ 11 스타일의 클래스 헤더 파일에서 unique_ptr의 기본값이 {nullptr}로 설정되어 있으면 위의 이유로 완전한 선언도 필요합니다.
feirainy

53

으로 알렉산더 C.가 언급 한 문제가 내려 오는 window유형이 곳의 소멸자가 암시 장소에서 정의되는 window_impl불완전 아직도있다. 그의 솔루션 외에도 내가 사용한 또 다른 해결 방법은 헤더에 Deleter functor를 선언하는 것입니다.

// Foo.h

class FooImpl;
struct FooImplDeleter
{
  void operator()(FooImpl *p);
};

class Foo
{
...
private:
  std::unique_ptr<FooImpl, FooImplDeleter> impl_;
};

// Foo.cpp

...
void FooImplDeleter::operator()(FooImpl *p)
{
  delete p;
}

사용자 정의 Deleter 함수를 사용하면 여기에서std::make_unique 이미 설명한대로 (C ++ 14에서 사용 가능)을 사용할 수 없습니다 .


6
이것이 내가 생각하는 한 올바른 해결책입니다. pimpl-idiom을 사용하는 것이 독특하지는 않습니다. 불완전한 클래스에서 std :: unique_ptr을 사용하는 일반적인 문제입니다. std :: unique_ptr <X>에서 사용하는 기본 삭제 기는 "X 삭제"를 시도합니다. X가 정방향 선언 인 경우에는 수행 할 수 없습니다. 삭제 기능을 지정하면 해당 기능을 클래스 X가 완전히 정의 된 소스 파일에 넣을 수 있습니다. X가 DeleterFunc를 포함하는 소스 파일과 연결되어있는 한 X는 단지 전방 선언이지만 다른 소스 파일은 std :: unique_ptr <X, DeleterFunc>를 사용할 수 있습니다.
sheltond

1
이는 "Foo"유형의 인스턴스 (예 : 생성자와 소멸자를 참조하는 정적 "getInstance"메소드)를 작성하는 인라인 함수 정의가 있어야하며이를 구현 파일로 이동하지 않으려는 경우에 좋은 해결 방법입니다. @ adspx5가 제안한대로.
GameSalutes

20

맞춤 삭제 도구 사용

문제는 자체 소멸자, 이동 할당 연산자 및 멤버 함수 unique_ptr<T>에서 소멸자 T::~T()를 호출해야 한다는 것입니다 unique_ptr::reset()(전용). 그러나 여러 PIMPL 상황 (이미 외부 클래스의 소멸자 및 이동 할당 연산자에서)에서 이러한 이름을 암시 적 또는 명시 적으로 호출해야합니다.

이미 다른 답변에서 지적한 바와 같이,이를 방지하는 한 가지 방법은 이동하는 모든 필요한 작업을 unique_ptr::~unique_ptr(), unique_ptr::operator=(unique_ptr&&)그리고 unique_ptr::reset()pimpl 도우미 클래스가 실제로 정의 된 소스 파일에.

그러나, 이것은 다소 불편하고, 핌필 이도 임의 요점을 어느 정도 무시한다. 커스텀 삭제기를 사용 하고 여드름 도우미 클래스가있는 소스 파일로만 정의를 옮기는 것을 피하는 훨씬 깨끗한 솔루션 . 다음은 간단한 예입니다.

// file.h
class foo
{
  struct pimpl;
  struct pimpl_deleter { void operator()(pimpl*) const; };
  std::unique_ptr<pimpl,pimpl_deleter> m_pimpl;
public:
  foo(some data);
  foo(foo&&) = default;             // no need to define this in file.cc
  foo&operator=(foo&&) = default;   // no need to define this in file.cc
//foo::~foo()          auto-generated: no need to define this in file.cc
};

// file.cc
struct foo::pimpl
{
  // lots of complicated code
};
void foo::pimpl_deleter::operator()(foo::pimpl*ptr) const { delete ptr; }

별도의 삭제 클래스 대신 무료 함수 또는 static멤버를 foo람다와 함께 사용할 수도 있습니다 .

class foo {
  struct pimpl;
  static void delete_pimpl(pimpl*);
  std::unique_ptr<pimpl,[](pimpl*ptr){delete_pimpl(ptr);}> m_pimpl;
};

15

아마도 불완전한 유형을 사용하는 클래스 내의 .h 파일 내에 일부 함수 본문이 있습니다.

.h for class 창 내에서 함수 선언 만 가지고 있는지 확인하십시오. window의 모든 함수 본문은 .cpp 파일에 있어야합니다. 그리고 window_impl도 ...

Btw, .h 파일에서 windows 클래스에 대한 소멸자 선언을 명시 적으로 추가해야합니다.

그러나 헤더 파일에 빈 dtor 본문을 넣을 수 없습니다.

class window {
    virtual ~window() {};
  }

단지 선언이어야합니다 :

  class window {
    virtual ~window();
  }

이것은 또한 나의 해결책이었습니다. 더 간결한 방법. 생성자 / 소멸자를 헤더로 선언하고 cpp 파일에 정의하십시오.
Kris Morness

2

내부 삭제 "유틸리티 라이브러리"에서 다른 사용자의 커스텀 삭제에 대한 답변을 추가하기 위해이 공통 패턴 ( std::unique_ptr불완전한 유형, 일부 TU에게만 알려진 불완전한 유형) 을 구현하기위한 도우미 헤더를 추가했습니다. 클라이언트에 대한 불투명 한 핸들).

이 패턴에 대한 공통 스캐 폴딩을 제공합니다. 외부 적으로 정의 된 삭제 기능을 호출하는 사용자 정의 삭제 unique_ptr클래스,이 삭제 클래스를 가진 유형 별명 , 및 TU에서 완전히 정의 된 TU에서 삭제 기능을 선언하는 매크로 유형. 나는 이것이 일반적인 유용성을 가지고 있다고 생각하므로 여기에 있습니다 :

#ifndef CZU_UNIQUE_OPAQUE_HPP
#define CZU_UNIQUE_OPAQUE_HPP
#include <memory>

/**
    Helper to define a `std::unique_ptr` that works just with a forward
    declaration

    The "regular" `std::unique_ptr<T>` requires the full definition of `T` to be
    available, as it has to emit calls to `delete` in every TU that may use it.

    A workaround to this problem is to have a `std::unique_ptr` with a custom
    deleter, which is defined in a TU that knows the full definition of `T`.

    This header standardizes and generalizes this trick. The usage is quite
    simple:

    - everywhere you would have used `std::unique_ptr<T>`, use
      `czu::unique_opaque<T>`; it will work just fine with `T` being a forward
      declaration;
    - in a TU that knows the full definition of `T`, at top level invoke the
      macro `CZU_DEFINE_OPAQUE_DELETER`; it will define the custom deleter used
      by `czu::unique_opaque<T>`
*/

namespace czu {
template<typename T>
struct opaque_deleter {
    void operator()(T *it) {
        void opaque_deleter_hook(T *);
        opaque_deleter_hook(it);
    }
};

template<typename T>
using unique_opaque = std::unique_ptr<T, opaque_deleter<T>>;
}

/// Call at top level in a C++ file to enable type %T to be used in an %unique_opaque<T>
#define CZU_DEFINE_OPAQUE_DELETER(T) namespace czu { void opaque_deleter_hook(T *it) { delete it; } }

#endif

1

최상의 해결책은 아니지만 때로는 shared_ptr을 대신 사용할 수도 있습니다 . 물론 그것은 약간 과잉이지만, unique_ptr은 C ++ 표준 제작자가 람다를 삭제기로 사용하기로 결정할 때까지 10 년 더 기다릴 것입니다.

또 다른 측면. 코드에 따라 파괴 단계에서 window_impl이 불완전 할 수 있습니다. 이것은 정의되지 않은 동작의 원인 일 수 있습니다. 이것을보십시오 : 왜, 불완전한 유형을 삭제하는 것이 정의되지 않은 행동입니까?

따라서 가능한 경우 가상 소멸자를 사용하여 모든 객체에 매우 기본 객체를 정의합니다. 그리고 당신은 거의 잘합니다. 시스템은 포인터에 대해 가상 소멸자를 호출하므로 모든 조상에 대해 정의해야합니다. 또한 상속 섹션의 기본 클래스를 가상으로 정의해야합니다 (자세한 내용은 이 항목 참조).

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