2014-12-12 5 views
38

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

#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?

+2

Этот вопрос, наконец, объяснил мне, почему удачи полезны. – Dan

ответ

47

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

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

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

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

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

+0

Использование decltype() действительно подчеркивает, какой дебетер вы хотите использовать, и мне это нравится, и, возможно, это еще один вопрос. Однако является ли тип '' extern '' '' '' '' void f (void *)} '' и '' void f (void *) '' другим? Я думал, что разница была только в имени символа. При очень аренде вы можете использовать те же функции указателей функций ... –

+4

@ Paolo.Bolzoni: Да, они разные, и вы не можете (переносимо) использовать один и тот же тип указателя. –

+3

@ Paolo.Bolzoni Несколько реализаций действительно жалуются на 'extern 'C" {void f(); } void (* fp)() = f; 'с сообщением о назначении из несовместимого типа указателя. Думаю, я помню, что читал, что HP - это одна из реализаций, которая отвергает ее, но я не могу проверить это. Sun (теперь Oracle) - это еще одна реализация, которая обращает на это внимание, но она допускает такие неявные преобразования с предупреждением (и что я видел себя). – hvd

7

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

Конечно, с помощью Говарда Hinnant tutorial on 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); 
} 
+5

Поскольку вы теперь сохраняете указатель на дескриптор, а не сам дескриптор, возможны проблемы с временем жизни объекта, если '' дескриптор' уничтожен до 'unique_ptr'. Вы пытаетесь решить эту проблему с помощью 'new handle', но в этом случае не забывайте, что' close_handler' нужно будет не просто закрыть дескриптор, но и освободить память, или у вас есть утечка. – hvd

+2

Другими словами, 'operator()' требует, чтобы 'delete handle' работал правильно. –

+0

Это работает отлично с функциями win32, например: 'std :: unique_ptr argv (CommandLineToArgvW (commandLine, & argc), :: LocalFree)' – kiewic

7

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

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

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(); 
} 

Demo.

+0

Звучит правильно, но если мне нужно напишите класс, подобный этому, возможно, используя _finally_ и lambda легче читать ... (_finally_ реализация на C++: http://stackoverflow.com/a/25510879/1876111) –

20

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

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

Если один имеет многие из этих C-API полученных указателей, что дополнительное пространство может быть обузой задерживающего принятия безопасных C++ RAII для кода C++ оберточного существующих интерфейсов 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) }; 

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

+0

Я знаю, что есть другие более общие варианты для обертывания удалите код в тип unique_ptr, но они могут быть более вовлечены и не так просты в понимании и реализации. – PeterSom

+0

Мне нравится этот ответ и согласен с большинством из них, за исключением того, что мой ответ вводит накладные расходы. Это не так: у него есть те же накладные расходы, что и в вопросе. Я просто не рассматривал этот аспект (и не думал об этом, по крайней мере, не при написании моего ответа). – hvd

+1

Да, исходный вопрос уже имел ненужные накладные расходы. И я добавлял свой ответ, потому что накладные расходы использовались в качестве предлога для того, чтобы не использовать unique_ptr для этой цели, и потому, что мое предложение было отклонено в списке рассылки std как что-то, что можно было поместить в стандарт C++. И да, до недели назад я также не думал об этом решении сознательно. – PeterSom