std :: unique_ptr 멤버와 함께 사용자 정의 삭제기를 사용하려면 어떻게합니까?


133

unique_ptr 멤버가있는 클래스가 있습니다.

class Foo {
private:
    std::unique_ptr<Bar> bar;
    ...
};

Bar는 create () 함수와 destroy () 함수가있는 타사 클래스입니다.

std::unique_ptr독립형 기능 으로 함께 사용하고 싶다면 다음을 수행 할 수 있습니다.

void foo() {
    std::unique_ptr<Bar, void(*)(Bar*)> bar(create(), [](Bar* b){ destroy(b); });
    ...
}

std::unique_ptr수업의 일원으로서 이것을 할 수있는 방법이 있습니까?

답변:


133

그 가정 create하고 destroy다음 서명을 (영업 이익의 코드의 경우 것으로 보인다) 무료 기능입니다 :

Bar* create();
void destroy(Bar*);

Foo이런 식으로 수업을 쓸 수 있습니다

class Foo {

    std::unique_ptr<Bar, void(*)(Bar*)> ptr_;

    // ...

public:

    Foo() : ptr_(create(), destroy) { /* ... */ }

    // ...
};

destroy이미 삭제 도구 이므로 여기에 람다 또는 사용자 지정 삭제 도구를 작성할 필요가 없습니다 .


156
C ++ 11 사용std::unique_ptr<Bar, decltype(&destroy)> ptr_;
Joe

1
이 솔루션의 단점은 모든 것의 오버 헤드를 두 배로 늘리는 것입니다 unique_ptr(모두 실제 포인터에 대한 포인터와 함께 함수 포인터를 저장해야 함). 매번 파괴 함수를 전달해야하며 인라인 할 수 없습니다 (템플릿이 특정 함수에 특화되고 서명 만 가능하며 포인터를 통해 함수를 호출해야합니다 (직접 호출보다 비용이 많이 듭니다). 두 RICIDeduplicator의 답변은 펑에 전문 이러한 모든 비용을 피하십시오.
ShadowRanger

@ShadowRanger는 명시 적으로 전달했는지 여부에 관계없이 default_delete <T> 및 저장 함수 포인터로 정의되지 않습니까?
헤르 고트

117

C ++ 11 (G ++ 4.8.2에서 테스트)의 람다를 사용 하여이 작업을 깨끗하게 수행 할 수 있습니다.

이 재사용이 가능하면 typedef:

template<typename T>
using deleted_unique_ptr = std::unique_ptr<T,std::function<void(T*)>>;

당신은 쓸 수 있습니다:

deleted_unique_ptr<Foo> foo(new Foo(), [](Foo* f) { customdeleter(f); });

예를 들어 FILE*:

deleted_unique_ptr<FILE> file(
    fopen("file.txt", "r"),
    [](FILE* f) { fclose(f); });

이를 통해 try / catch 노이즈없이 RAII를 사용하여 예외 안전 클린업의 이점을 얻을 수 있습니다.


2
이것이 답이되어야합니다. 더 아름다운 솔루션입니다. 또는 std::function정의에있는 것과 같은 단점이 있습니까?
j00hi

17
@ j00hi, 내 의견으로는이 솔루션으로 인해 불필요한 오버 헤드가 std::function있습니다. 이 솔루션과 달리 허용되는 답변의 Lambda 또는 사용자 정의 클래스를 인라인 할 수 있습니다. 그러나이 방법은 모든 구현을 전용 모듈로 격리하려는 경우에 이점이 있습니다.
magras

5
std :: function 생성자가 throw하면 메모리가 누출됩니다 (
stlam

4
람다는 실제로 여기에 필요합니까? 규칙을 따르는 deleted_unique_ptr<Foo> foo(new Foo(), customdeleter);경우 간단 할 수 있습니다 customdeleter(void를 반환하고 원시 포인터를 인수로 허용합니다).
Victor Polevoy

이 방법에는 한 가지 단점이 있습니다. std :: function은 가능할 때마다 이동 생성자를 사용하지 않아도됩니다. 이는 std :: move (my_deleted_unique_ptr)를 사용할 때 람다로 묶인 내용이 이동하는 대신 복사 될 수 있음을 의미합니다.
GeniusIsme

71

삭제 클래스를 작성하면됩니다.

struct BarDeleter {
  void operator()(Bar* b) { destroy(b); }
};

의 템플릿 인수로 제공하십시오 unique_ptr. 여전히 생성자에서 unique_ptr을 초기화해야합니다.

class Foo {
  public:
    Foo() : bar(create()), ... { ... }

  private:
    std::unique_ptr<Bar, BarDeleter> bar;
    ...
};

내가 아는 한, 모든 인기있는 c ++ 라이브러리는 이것을 올바르게 구현합니다. 이후 BarDeleter 실제로 어떤 상태가없는, 그것은 어떤 공간을 점유 할 필요가 없습니다 unique_ptr.


8
이 옵션은 0 매개 변수 std :: unique_ptr constructor를 사용할 수 있으므로 배열, std :: vector 및 기타 컬렉션에서 작동하는 유일한 옵션입니다. 다른 답변은 고유 포인터를 구성 할 때 Deleter 인스턴스를 제공해야하기 때문에이 0 매개 변수 생성자에 액세스 할 수없는 솔루션을 사용합니다. 그러나이 솔루션은 생성자가 자체적으로 Deleter 인스턴스를 작성할 수 있는 Deleter 클래스 ( struct BarDeleter) ~ std::unique_ptr( std::unique_ptr<Bar, BarDeleter>)를 제공합니다 std::unique_ptr. 즉 다음 코드가 허용됩니다std::unique_ptr<Bar, BarDeleter> bar[10];
DavidF

13
쉽게 사용할 수 있도록 typedef를 작성하겠습니다typedef std::unique_ptr<Bar, BarDeleter> UniqueBarPtr
DavidF

@DavidF : 또는 사용 Deduplicator의 접근 방식 같은 장점이있다 (인라인 삭제, 각각에 별도의 저장 unique_ptr, 필요 구성 할 때 Deleter가의 인스턴스를 제공 없습니다), 사용할 수있는 이점이 추가 std::unique_ptr<Bar>기억 할 필요없이 어디서나 특수 typedef하거나 명시 적으로 공급자에게 두 번째 템플릿 매개 변수를 사용합니다. (명확하게하기 위해이 좋은 솔루션입니다, I 최대 - 투표,하지만 하나의 단계는 원활한 용액 부끄러워 정지)
ShadowRanger

22

런타임에 삭제 프로그램을 변경할 수 없다면 사용자 지정 삭제 유형을 사용하는 것이 좋습니다. 예를 들어, Deleter가에 대한 함수 포인터를 사용하는 경우 sizeof(unique_ptr<T, fptr>) == 2 * sizeof(T*). 즉, 바이트의 절반unique_ptr 객체 이 낭비됩니다.

그러나 모든 기능을 래핑하기 위해 사용자 정의 삭제기를 작성하는 것은 귀찮습니다. 고맙게도 함수에 템플릿 형식을 작성할 수 있습니다.

C ++ 17부터 :

template <auto fn>
using deleter_from_fn = std::integral_constant<decltype(fn), fn>;

template <typename T, auto fn>
using my_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;

// usage:
my_unique_ptr<Bar, destroy> p{create()};

C ++ 17 이전 :

template <typename D, D fn>
using deleter_from_fn = std::integral_constant<D, fn>;

template <typename T, typename D, D fn>
using my_unique_ptr = std::unique_ptr<T, deleter_from_fn<D, fn>>;

// usage:
my_unique_ptr<Bar, decltype(destroy), destroy> p{create()};

맵시 있는. 나는 이것이 상용구가 적고 rici의 대답 에서 functor와 동일한 이점 (메모리 포인터를 절반으로 줄이고 함수 포인터를 사용하지 않고 직접 함수를 호출하는 것)을 얻는 것이 맞 습니까?
ShadowRanger

예, 이것은 맞춤 삭제 클래스의 장점을 모두 제공해야합니다 deleter_from_fn.
rmcclellan

7

사용자 정의 삭제기를 사용하는 것이 코드 전체에서 언급해야하므로 최선의 방법은 아닙니다.
대신, 사용자 정의 유형이 포함되고 의미를 존중하는 한 네임 스페이스 수준 클래스에 전문화를 추가 할 수 있으므로 다음과 같이하십시오::std .

전문화하십시오 std::default_delete:

template <>
struct ::std::default_delete<Bar> {
    default_delete() = default;
    template <class U, class = std::enable_if_t<std::is_convertible<U*, Bar*>()>>
    constexpr default_delete(default_delete<U>) noexcept {}
    void operator()(Bar* p) const noexcept { destroy(p); }
};

그리고 아마도 할 수도 있습니다 std::make_unique():

template <>
inline ::std::unique_ptr<Bar> ::std::make_unique<Bar>() {
    auto p = create();
    if (!p) throw std::runtime_error("Could not `create()` a new `Bar`.");
    return { p };
}

2
나는 이것을 매우 조심할 것입니다. 개방 std하면 완전히 새로운 웜 캔이 열립니다. 또한 std::make_uniqueC ++ 20 std은 클래스 템플릿이 아닌 것들 ( std::make_unique기능 템플릿) 의 전문화를 허용하지 않기 때문에 C ++ 20 이후 에는 전문화 가 허용되지 않습니다 (따라서 이전에 수행해서는 안 됨 ). 전달 된 포인터가 std::unique_ptr<Bar>에서 할당되지 않은 경우 create()다른 할당 기능에서 UB로 끝날 수도 있습니다.
Justin

이것이 허용된다고 확신하지 않습니다. 이 전문화가 std::default_delete원본 템플릿의 요구 사항을 충족 한다는 것을 증명하기가 어려운 것처럼 보입니다 . 나는 그것이 std::default_delete<Foo>()(p)쓸 수있는 올바른 방법 이라고 생각할 것 입니다 delete p;. 그래서 쓰기 에 delete p;유효하다면 (즉, Foo완성 된 경우 ), 이것은 같은 행동이 아닙니다. 또한 delete p;쓰기가 유효하지 않은 경우 ( Foo불완전한 경우) 동작을 std::default_delete<Foo>동일하게 유지하는 대신에 새로운 동작을 지정 합니다.
Justin

make_unique전문화는 문제가있다,하지만 난 확실히 사용했습니다 std::default_delete과부하 (와 템플릿하지 enable_if단지에는 OpenSSL의 같은 C 구조체를 들어, BIGNUM서브 클래 싱이 발생하지 않을 알려진 파괴 기능을 사용), 그리고 같은, 가장 쉬운 접근 방법에 의해입니다 코드의 나머지 부분은 사용할 수 unique_ptr<special_type>(가) 템플릿으로 펑터 유형을 통과 할 필요없이 Deleter모든 이상도 사용 typedef/ using그 문제를 방지한다고 유형에 이름을 부여 할 수 있습니다.
ShadowRanger

6

std::bind파괴 기능과 함께 간단히 사용할 수 있습니다 .

std::unique_ptr<Bar, std::function<void(Bar*)>> bar(create(), std::bind(&destroy,
    std::placeholders::_1));

그러나 물론 람다를 사용할 수도 있습니다.

std::unique_ptr<Bar, std::function<void(Bar*)>> ptr(create(), [](Bar* b){ destroy(b);});
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.