Подтвердить что ты не робот

Std:: unique_ptr для функций C, которые нужны бесплатно

Подумайте о функции C, возвращающей то, что должно быть free d, например POSIX strdup(). Я хочу использовать эту функцию в С++ 11 и избегать любых утечек, это правильный способ?

#include <memory>
#include <iostream>
#include <string.h>

int main() {
    char const* t { "Hi stackoverflow!" };
    std::unique_ptr<char, void(*)(void*)>
        t_copy { strdup(t), std::free };

    std::cout << t_copy.get() << " <- this is the copy!" <<std::endl;
}

Предполагая, что это имеет смысл, можно использовать аналогичный шаблон с не указателями? Например, для функции POSIX open, которая возвращает int?

4b9b3361

Ответ 1

То, что у вас есть, с большой вероятностью будет работать на практике, но не строго корректно. Однако правильная версия, возможно, еще более читаема:

std::unique_ptr<char, decltype(std::free) *>
    t_copy { strdup(t), std::free };

Причина в том, что тип функции std::free не гарантируется void(void*). Гарантируется принятие void* и возврат void, но есть два типа функций, которые соответствуют этой спецификации: одна с C-связью и одна с С++-связью. Большинство компиляторов не обращают на это внимания, но для правильности вам следует избегать предположений об этом.

Предполагая, что это имеет смысл, можно использовать аналогичный шаблон с не указателями?

Не с unique_ptr, что действительно специфично для указателей. Но вы можете создать свой собственный класс, похожий на unique_ptr, но без предположений об обертываемом объекте.

Ответ 2

В исходном вопросе (и hvd answer) вводятся служебные данные с указателем, поэтому такой unique_ptr в два раза больше, чем один, полученный с помощью std::make_unique. Кроме того, я бы сразу сформулировал decltype:

std::unique_ptr<char, decltype(&std::free)>
    t_copy { strdup(t), &std::free };

Если у многих из этих указателей, основанных на C-API, дополнительное пространство может стать бременем, препятствующим принятию безопасного кода С++ RAII для кода С++, обертывающего существующие API-интерфейсы типа POSIX, требующие быть free() d. Другая проблема, которая может возникнуть, заключается в том, что при использовании char const в приведенной выше ситуации вы получаете ошибку компиляции, потому что вы не можете автоматически преобразовать char const * в тип параметра free(void *).

Я предлагаю использовать выделенный тип делетера, а не встроенный на лету, так что пространство над головой уходит, а требуемый const_cast также не является проблемой. Затем псевдоним шаблона можно легко использовать для переноса указателей на C-API:

struct free_deleter{
    template <typename T>
    void operator()(T *p) const {
        std::free(const_cast<std::remove_const_t<T>*>(p));
    }
};
template <typename T>
using unique_C_ptr=std::unique_ptr<T,free_deleter>;
static_assert(sizeof(char *)==
              sizeof(unique_C_ptr<char>),""); // ensure no overhead

Теперь пример станет

unique_C_ptr<char const> t_copy { strdup(t) };

Я предлагаю, чтобы этот механизм был доступен в библиотеке поддержки основных принципов С++ (возможно, с лучшим наименованием), поэтому он может быть доступен для баз кода, переходящих на современный С++ (успех открыт).

Ответ 3

Предполагая, что это имеет смысл, можно использовать аналогичную модель с не-указатели? Например, для открытия функции POSIX, которая возвращает int?

Конечно, используя Howard Hinnant учебник по unique_ptr, мы можем увидеть мотивирующий пример:

// For open
#include <sys/stat.h>
#include <fcntl.h>

// For close
#include <unistd.h>

// For unique_ptr
#include <memory>

int main()
{
    auto handle_deleter = [] (int* handle) {
        close(*handle);
    };

    int handle = open("main.cpp", O_RDONLY);
    std::unique_ptr<int, decltype(handle_deleter)> uptr
        { &handle, handle_deleter };
}

В качестве альтернативы вы можете использовать функтор вместо лямбда:

struct close_handler
{
    void operator()(int* handle) const
    {
        close(*handle);
    }
};

int main()
{
    int handle = open("main.cpp", O_RDONLY);
    std::unique_ptr<int, close_handler> uptr
        { &handle };
}

Пример может быть дополнительно уменьшен, если мы используем функцию typedef и "factory".

using handle = int;
using handle_ptr = std::unique_ptr<handle, close_handler>;

template <typename... T>
handle_ptr get_file_handle(T&&... args)
{
    return handle_ptr(new handle{open(std::forward<T>(args)...)});
}

int main()
{
    handle_ptr hp = get_file_handle("main.cpp", O_RDONLY);
}

Ответ 4

Предполагая, что это имеет смысл, можно использовать аналогичную модель с не-указатели? Например, для открытия функции POSIX, которая возвращает int?

Да, это можно сделать. Вам нужен тип "указатель", который удовлетворяет требованиям NullablePointer:

struct handle_wrapper {

    handle_wrapper() noexcept : handle(-1) {}
    explicit handle_wrapper(int h) noexcept : handle(h) {}
    handle_wrapper(std::nullptr_t)  noexcept : handle_wrapper() {}

    int operator *() const noexcept { return handle; }
    explicit operator bool() const noexcept { return *this != nullptr; }

    friend bool operator!=(const handle_wrapper& a, const handle_wrapper& b) noexcept {
        return a.handle != b.handle;
    }

    friend bool operator==(const handle_wrapper& a, const handle_wrapper& b) noexcept {
        return a.handle == b.handle;
    }

    int handle;
};

Обратите внимание, что здесь мы используем -1 как значение нулевого дескриптора, потому что то, что open() возвращается при ошибке. Также очень легко templatize этот код, чтобы он принимал другие типы дескрипторов и/или недопустимые значения.

Тогда

struct posix_close
{
    using pointer = handle_wrapper;
    void operator()(pointer fd) const
    {
        close(*fd);
    }
};

int
main()
{
    std::unique_ptr<int, posix_close> p(handle_wrapper(open("testing", O_CREAT)));
    int fd = *p.get();
}

Демо.