Как запустить std :: unique_ptr в конструкторе с помощью std :: make_unique () [duplicate]

Прямо из документации Apple: Создание CGPathRef из объекта NSBezierPath

Вот соответствующий код.

@implementation NSBezierPath (BezierPathQuartzUtilities)
// This method works only in OS X v10.2 and later.
- (CGPathRef)quartzPath
{
    int i, numElements;

    // Need to begin a path here.
    CGPathRef           immutablePath = NULL;

    // Then draw the path elements.
    numElements = [self elementCount];
    if (numElements > 0)
    {
        CGMutablePathRef    path = CGPathCreateMutable();
        NSPoint             points[3];
        BOOL                didClosePath = YES;

        for (i = 0; i < numElements; i++)
        {
            switch ([self elementAtIndex:i associatedPoints:points])
            {
                case NSMoveToBezierPathElement:
                    CGPathMoveToPoint(path, NULL, points[0].x, points[0].y);
                    break;

                case NSLineToBezierPathElement:
                    CGPathAddLineToPoint(path, NULL, points[0].x, points[0].y);
                    didClosePath = NO;
                    break;

                case NSCurveToBezierPathElement:
                    CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y,
                                        points[1].x, points[1].y,
                                        points[2].x, points[2].y);
                    didClosePath = NO;
                    break;

                case NSClosePathBezierPathElement:
                    CGPathCloseSubpath(path);
                    didClosePath = YES;
                    break;
            }
        }

        // Be sure the path is closed or Quartz may not do valid hit detection.
        if (!didClosePath)
            CGPathCloseSubpath(path);

        immutablePath = CGPathCreateCopy(path);
        CGPathRelease(path);
    }

    return immutablePath;
}
@end

Bug Reporter

]

rdar: // 15758302 : NSBezierPath для CGPath.

88
задан huitlarc 27 September 2013 в 15:31
поделиться

6 ответов

Предполагая, что create и destroy являются свободными функциями (что, по-видимому, имеет место из фрагмента кода OP) со следующими сигнатурами:

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

Вы можете написать свой класс Foo как это

class Foo {

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

    // ...

public:

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

    // ...
};

Обратите внимание, что вам не нужно писать какой-либо лямбда или пользовательский делетер, потому что destroy уже является deleter.

88
ответ дан Cassio Neri 18 August 2018 в 15:18
поделиться

Вы знаете, использование пользовательского делетера - не лучший способ, так как вам придется упомянуть его по всему вашему коду. Вместо этого , поскольку вам разрешено добавлять специализации к классам уровня пространства имен в ::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 };
}
4
ответ дан Deduplicator 18 August 2018 в 15:18
поделиться
  • 1
    Я был бы очень осторожен с этим. Открытие std открывает совершенно новую банку червей. Также обратите внимание, что специализация std::make_unique не допускается post C ++ 20 (поэтому этого не следует делать раньше), потому что C ++ 20 запрещает специализацию вещей в std, которые не являются шаблонами классов (std::make_unique шаблон функции). Обратите внимание, что вы также, вероятно, закончите с UB, если указатель, переданный в std::unique_ptr<Bar>, не был выделен из create(), но из какой-либо другой функции распределения. – Justin 10 July 2018 в 20:18
  • 2
    Я не уверен, что это разрешено. Мне кажется, что трудно доказать, что эта специализация std::default_delete соответствует требованию исходного шаблона. Я бы предположил, что std::default_delete<Foo>()(p) будет правильным способом записи delete p;, поэтому, если delete p; будет действительным для записи (т. Е. Если Foo будет завершен), это не будет тем же самым поведением. Кроме того, если delete p; недействителен для записи (Foo является неполным), это будет определять новое поведение для std::default_delete<Foo>, а не сохранять поведение одинаковым. – Justin 10 July 2018 в 21:08

Это можно сделать с помощью lambda в 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); });

При этом вы получаете преимущества безопасной от исключения очистки с использованием RAII, без шума try / catch.

74
ответ дан Drew Noakes 18 August 2018 в 15:18
поделиться
  • 1
    Это должен быть ответ, имо. Это более красивое решение. Или есть недостатки, например, например. с std::function в определении или так? – j00hi 8 July 2015 в 10:38
  • 2
    @ j00hi, на мой взгляд, это решение имеет ненужные накладные расходы из-за std::function. В отличие от этого решения может быть встроен лямбда или пользовательский класс, как в принятом ответе. Но этот подход имеет преимущество в том случае, если вы хотите изолировать всю реализацию в выделенном модуле. – magras 12 October 2015 в 17:57
  • 3
    Это приведет к утечке памяти, если std :: function constructor выбрасывает (что может произойти, если лямбда слишком велика для размещения внутри объекта std :: function) – Ivan 15 March 2016 в 13:48
  • 4
    Действительно ли лямбда требует здесь? Это может быть просто deleted_unique_ptr<Foo> foo(new Foo(), customdeleter);, если customdeleter следует за соглашением (он возвращает void и принимает необработанный указатель в качестве аргумента). – Victor Polevoy 5 September 2017 в 08:47
  • 5
    Существует один недостаток этого подхода. Функция std :: function не обязательно использовать конструктор перемещения, когда это возможно. Это означает, что когда вы std :: move (my_deleted_unique_ptr), содержимое, заключенное в lambda, возможно, будет скопировано вместо перемещения, что может или не может быть тем, что вы хотите. – GeniusIsme 27 September 2017 в 12:26

Если вам не удастся изменить делетера во время выполнения, я настоятельно рекомендую использовать настраиваемый тип удаления. Например, если вы используете указатель функции для вашего делета, 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()};
1
ответ дан Justin 18 August 2018 в 15:18
поделиться

Вы можете просто использовать std::bind с вашей функцией destroy.

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);});
3
ответ дан mkaes 18 August 2018 в 15:18
поделиться

Вам просто нужно создать класс deleter:

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.

43
ответ дан rici 18 August 2018 в 15:18
поделиться
  • 1
    этот параметр является единственным, который работает с массивами, std :: vector и другими коллекциями, поскольку он может использовать конструктор std :: unique_ptr с нулевым параметром. другие ответы используют решения, которые не имеют доступа к этому конструктору нулевых параметров, потому что при построении уникального указателя должен быть предоставлен экземпляр Deleter. Но это решение предоставляет класс Deleter (struct BarDeleter) до std::unique_ptr (std::unique_ptr<Bar, BarDeleter>), который позволяет конструктору std::unique_ptr создать экземпляр Deleter самостоятельно. то есть допустим следующий код std::unique_ptr<Bar, BarDeleter> bar[10]; – DavidF 8 July 2016 в 11:47
  • 2
    Я бы создал typedef для удобства использования typedef std::unique_ptr<Bar, BarDeleter> UniqueBarPtr – DavidF 8 July 2016 в 11:51
Другие вопросы по тегам:

Похожие вопросы: