3 ms·
Your C API here takes a `T*` but I think that the intended use for these new functions is with C APIs that take `T**`. Those are somewhat inconvenient to make
by abbeyj 4y ago
Your C API here takes a `T*` but I think that the intended use for these new functions is with C APIs that take `T**`. Those are somewhat inconvenient to make interoperate with smart pointers. For example, in C you might have something like this:
void c_api_that_returns_via_an_out_parameter(T** ret) {
*ret = new T();
}
T* p;
c_api_that_returns_via_an_out_parameter(&p);
Then you might try to change this to use a smart pointer as:
std::unique_ptr<T> p;
c_api_that_returns_via_an_out_parameter(&p);
But this won't compile because you can't take the address of a `std::unique_ptr<T>` and then pass it where a `T**` is expected. So instead you need to do something like this:
T* tmp = 0;
c_api_that_returns_via_an_out_parameter(&tmp);
std::unique_ptr<T> p(tmp);
That can be a bit of a pain. These new functions serve as adapters to make this easier. They take in a `std::unique_ptr<T>` and provide a `T*` that you can pass to the C API:
std::unique_ptr<T> p;
c_api_that_returns_via_an_out_parameter(std::out_ptr(p));
No temporary needed.
If your C API only ever passes and returns `T*` and never uses `T**` then I think that you don't need these new functions and you can continue to do things the current way. For instance, if you have a C API that allocates a new object and then returns a pointer to it then you can take this return value and immediately store it into your smart pointer:
std::unique_ptr<T> p(c_api_that_returns_a_pointer());
And if you have a C API that takes a `T**` and then deallocates it:
c_api_that_deallocates(p.release());
Although really your smart pointer should be doing this for you so you'd never need to do it explicitly. If you're ever manually calling `.release()` then that indicates a place where the smart pointer abstraction has broken down. You want to minimize or eliminate that. Note that in the examples using the new functions there are no explicit calls to `.release()` anywhere and this is a good thing.
I can't recall ever seeing a C API that conditionally deallocates the pointer passed in. Do you have an example? Most C APIs that I'm familiar with have one function that allocates a new object and a separate function that unconditionally deallocates it, acting analogously to `malloc` and `free`.
- dataflow 4y ago> If you're ever manually calling `.release()` then that indicates a place where the smart pointer abstraction has broken down. You want to minimize or eliminate that. Yes, that's life when you're at the boundary with a C API. > I can't recall ever seeing a C API that conditionally deallocates the pointer passed in. Do you have an example? realloc()? Generally when you have an in-out parameter, it's doing more than just releasing resources, so there's a potential for failure. And in the failure case, the caller will often retain ownership of any pointers it passed. In fact, off the top of my head, I can only think of one function that is contrary to this, and that is DeferWindowPos() in Windows, which invalidates the input handle even on failure.
- abbeyj 4y ago> realloc()? OK, I'll give you that one, but that's a rather unusual case. It has complicated semantics. It is unlikely to interoperate well with any smart pointer without careful, manual work. It doesn't take a `T**` so it doesn't seem relevant to discussions of std::out_ptr though? You won't be able to use std::out_ptr with realloc so there's no chance of making a mistake with it. > Generally when you have an in-out parameter, it's doing more than just releasing resources I'm still confused. What function do you have that is both releasing resources and also doing other work which might fail? Even something like `fclose` which needs to flush buffers and then release resources will always release the resources, even if the flush fails. I'm more familiar with APIs that have: - One (or more) functions to explicitly allocate the object - Many functions that do work with that object and that might fail but that will never deallocate the object - One function to explicitly deallocate the object For example, SQLite has sqlite3_open to create a new database object, sqlite3_prepare, sqlite3_exec, etc. to operate on the database object, and sqlite3_close to deallocate the database object.
- dataflow 4y ago> It doesn't take a `T**` so it doesn't seem relevant to discussions of std::out_ptr though? You won't be able to use std::out_ptr with realloc so there's no chance of making a mistake with it. [...] The problem is the same, it just happens to not take a T**. The problem is the same for any function that takes T**, I just couldn't think of one off the top of my head. I listed some below now. But also note that the absence of many APIs taking T** would also be an argument for these smart pointers not being so useful, so it's not really a counterargument! > What function do you have that is both releasing resources and also doing other work which might fail? See for example getdelim() or even asprintf(). asprintf() isn't guaranteed to return a valid pointer due to an error, so you can't free it unconditionally. getdelim() also isn't guaranteed to free the input if there is an I/O error, so you can't release that unconditionally either. It would be helpful if you try to list some functions you believe you can use std::inout_ptr on.
- abbeyj 4y ago