2

While this should be a trivial question I was unable to find an answer this far. In C APIs there are lots of functions that take pointers and pointers to pointers as arguments. How can I use PROPERLY smart pointers as arguments with C APIs.

Here is an example that I'd like to convert to using std::unique_ptr:

FMOD_SYSTEM* system = nullptr;
result = FMOD_System_Create(&system); // Create the main system object

FMOD_SOUND* musicStream;
result = FMOD_System_CreateSound(system,
                                 musicStreamPath,
                                 FMOD_CREATESTREAM,
                                 nullptr,
                                 &musicStream);

Reference: FMOD_System_Create FMOD_System_CreateSound

I start declaring the smart pointers as:

std::unique_ptr<FMOD_SYSTEM> system = nullptr;
std::unique_ptr<FMOD_SOUND> musicStream = nullptr;

Here are the compiler errors if I use .get():

cannot convert 'std::unique_ptr::pointer {aka FMOD_SOUND*}' to 'FMOD_SOUND**' for argument '5' to 'FMOD_RESULT FMOD_System_CreateSound(FMOD_SYSTEM*, const char*, FMOD_MODE, FMOD_CREATESOUNDEXINFO*, FMOD_SOUND**)' musicStream.get());

                                                   ^
4
  • 1
    Use the .get() method to get the underlying pointer? Commented Dec 28, 2014 at 21:59
  • Does not work with just .get(). There seems to be more to it but I cannot figure it out yet. Commented Dec 28, 2014 at 22:05
  • There's a real question and answer here. I'm working on it. Commented Dec 28, 2014 at 22:34
  • In any event, unless you are actually supposed to be calling delete on that pointer (pretty unlikely if it's returned by a C API), you have to supply a custom deleter. Commented Dec 29, 2014 at 10:04

3 Answers 3

4

Your problem is because that C API expects you to pass it the address of a pointer to an FMOD_SYSTEM, so that the API can fill in that pointer with the result — i.e., it takes the FMOD_SYSTEM* as an out-parameter.

In C++, the idiomatic way to do that would be to pass a reference to a (smart?) pointer to an FMOD_SYSTEM, i.e., where the C API is

FMOD_RESULT FMOD_System_Create(FMOD_SYSTEM **system);

FMOD_SYSTEM *system;
result = FMOD_System_Create(&system);

the C++ API would be

FMOD_RESULT FMOD_System_Create(std::unique_ptr<FMOD_SYSTEM> &system);

std::unique_ptr<FMOD_SYSTEM> system;
result = FMOD_System_Create(system);

However, there's a big problem with this C++ API! The problem is that creating an FMOD_SYSTEM and wrapping it in a unique_ptr are separate concerns, and shouldn't be mashed together like this. For example, what if I'm doing something clever with threading and really need my FMOD_SYSTEM to be managed by a shared_ptr instead of simply a unique_ptr? I have to create a unique_ptr to pass as the out-parameter and then std::move it into a shared_ptr afterward? That's both ugly and (micro-)inefficient.

std::unique_ptr<FMOD_SYSTEM> fake_system;
result = FMOD_System_Create(fake_system);
std::shared_ptr<FMOD_SYSTEM> system(std::move(fake_system));

The answer is to recognize that the root of the problem is out-parameters themselves, and that the solution is value semantics. The idiomatic C++ syntax we want to write is

auto system = std::make_unique<fmod_system>();

and the way we obtain that syntax is by wrapping up the C API's raw pointers in value classes:

class fmod_system {
    FMOD_SYSTEM *ptr;
    fmod_system() {
        auto result = FMOD_System_Create(&ptr);
        if (result != FMOD_OK) {
            ptr = nullptr;
            throw something;
        }
    }
    fmod_system(fmod_system&&) = default;
    fmod_system& operator=(fmod_system&&) = default;
    fmod_system(const fmod_system&) = delete;
    fmod_system& operator=(const fmod_system&) = delete;
    ~fmod_system() {
        auto result = FMOD_System_Release(ptr);
        assert(result == FMOD_OK);  // destructors shouldn't throw: use your best judgment here
    }
};

And in fact at this point our callers can drop the unique_ptr obfuscation and simply write

fmod_system system;

unless they really need the extra layer of pointer semantics for some reason.

Sign up to request clarification or add additional context in comments.

4 Comments

In that context. Is there a general rule of thumb how to use C APIs in a proper C++ style?
@Zingam: The hot phrase right now is "value semantics"; the older and almost synonymous phrase is "RAII". Googling both of those should turn up some good rules of thumb. If that doesn't help, I'd recommend posting another question — and make it as narrowly focused as possible. And/or, try writing the code yourself and then post it over on CodeReview Stack Exchange for feedback!
While both terms are good search hints, value semantics and RAII are two mostly orthogonal concepts. Value semantics is about (simplified) to make your types behave like ints (in particular with respect to copy semantics). RAII is a technique to make sure ressources get automatically released
@MikeMB Absolutely correct; although then I went back and read my answer and I guess I still think "value semantics and RAII are [similar]" is true in this particular context. RAII is about writing good destructors; value semantics is about writing good special member functions in general; both are about how to avoid exposing shared_ptr/raw-pointer antics to the end-user the way Zingam was originally doing. (That is: in this context I'm gonna stick to my assertion that they're more "almost synonymous" than they are "mostly orthogonal". I think we both agree it's not 100% or 0%.)
3

In general, I don't believe it is a good idea to mix C-APIs wiht smart pointers and the better approach is to wrap the C-API in a C++ Class as Quuxplusone suggested.

However, to answer your question, the "cleanest" (but still ugly way) I can think of is to pass the raw pointer to a smartpointer with a custom deleter, after the object has been created.

struct FMOD_SYSTEM_Deleter {    
    void operator()(FMOD_SYSTEM* sys) {
         if (sys !=  nullptr) {
             FMOD_System_Release(sys);
         }
    }
}

FMOD_SYSTEM* tsys = nullptr;
result = FMOD_System_Create(&tsys);
std::unique_ptr<FMOD_SYSTEM,FMOD_SYSTEM_Deleter> system(tsys);

Comments

2

Simple answer would be: C++ Smart Pointers are used for self allocated objects you would create with new and delete. They are not build for pointers to objects witch are created internally by 3rd party C style API functions and need to be released by another C Style API function. In your case you will have to call System::release and not delete, so using a smart pointer might cause big problems.

You have three possibilities:

  1. Just stay with the C Style and take care of calling release yourself. (I would prefer this)
  2. Write your own C++ wrapper class - like Quuxplusone suggested
  3. Modify the C++ smart pointer with a custom "allocator" and "deleter" (This is complicated, theoretical and not recommended)

1 Comment

Nothing theoretical about using a c++ smpartpointer with a costum deleter (And arguably it is also not too complicated).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.