std::unique_ptr for C functions that need free

前端 未结 4 464
攒了一身酷
攒了一身酷 2020-12-01 06:58

Think to a C function that return something that must be freed, for example the POSIX\'s strdup(). I want to use that function in C++11 and avoid a

相关标签:
4条回答
  • 2020-12-01 07:36

    What you have is extremely likely to work in practice, but not strictly correct. You can make it even more likely to work as follows:

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

    The reason is that the function type of std::free is not guaranteed to be void(void*). It is guaranteed to be callable when passed a void*, and in that case to return void, but there are at least two function types that match that specification: one with C linkage, and one with C++ linkage. Most compilers pay no attention to that, but for correctness, you should avoid making assumptions about it.

    However, even then, this is not strictly correct. As pointed out by @PeterSom in the comments, C++ allows implementations to e.g. make std::free an overloaded function, in which case both your and my use of std::free would be ambiguous. This is not a specific permission granted for std::free, it's a permission granted for pretty much any standard library function. To avoid this problem, a custom function or functor (as in his answer) is required.

    Assuming it makes sense, it is possible to use a similar pattern with non-pointers?

    Not with unique_ptr, which is really specific to pointers. But you could create your own class, similar to unique_ptr, but without making assumptions about the object being wrapped.

    0 讨论(0)
  • 2020-12-01 07:42

    Assuming it makes sense, it is possible to use a similar pattern with non-pointers? For example for the POSIX's function open that returns an int?

    Yes, it can be done. You need a "pointer" type that satisfies 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;
    };
    

    Note that we use -1 as the null handle value here because that's what open() returns on failure. It's also very easy to templatize this code so that it accepts other handle types and/or invalid values.

    Then

    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 讨论(0)
  • 2020-12-01 07:56

    The original question (and hvd's answer) introduce a per-pointer overhead, so such a unique_ptr is twice the size than one derived with std::make_unique. In addition, I would formulate the decltype directly:

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

    If one has many of those C-API-derived pointers that extra space can be a burden hindering adoption of safe C++ RAII for C++ code wrapping existing POSIX style APIs requiring to be free()d. Another problem that can arise, is when you use char const in the above situation, you get a compile error, because you can not automatically convert a char const * to the Parameter type of free(void *).

    I suggest to use a dedicated deleter type, not one built on the fly, so that the space overhead goes away and the required const_cast is also not a problem. A template alias then can easily be used to wrap C-API-derived pointers:

    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
    

    The example now becomes

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

    I will suggest that that mechanism should be made available in the C++ Core Guidelines support library ( maybe with a better naming ), so it can be made available for code bases transitioning to modern C++ ( success open ).

    0 讨论(0)
  • 2020-12-01 07:57

    Assuming it makes sense, it is possible to use a similar pattern with non-pointers? For example for the POSIX's function open that returns an int?

    Sure, using Howard's Hinnant tutorial on unique_ptr, we can see a motivating example:

    // 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 };
    }
    

    Alternatively you can use a functor instead of a lambda:

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

    The example can be further reduced if we use a typedef and a "factory" function.

    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);
    }
    
    0 讨论(0)
提交回复
热议问题