Search code examples
c++pointerssdl-2smart-pointers

Why I'm getting "invalid use of incomplete type" error when I change raw pointer to unique_pointer?


I'm making some SDL2 wrappers in C++. Like this:

/* header file */
#include <SDL_mixer.h>
#include <memory>

class SDL2_Music {
 public:
  ~SDL2_Music() { free(); }
  bool loadMusic(const std::string& path);
  bool play(int loops = -1);
  // more methods
 private:
  void free();
  Mix_Music* music_ = nullptr;
};

/* cpp file */
void SDL2_Music::free() {
  if (music_ != nullptr) {
    Mix_FreeMusic(music_);
    music_ = nullptr;
  }
}
bool SDL2_Music::loadMusic(const std::string& path) {
  free();
  music_ = Mix_LoadMUS(path.c_str()); // this returns a Mix_Music*
  if (music_ == nullptr) {
    ktp::logSDLError("Mix_LoadMUS");
    return false;
  }
  return true;
}
// more stuff

This works fine, but I want to get rid of the raw pointer, so I can also get rid of the free() method and the dtor invoking it (yes, I'm reading about the rule of 0). So I made the following changes:

/* header file */
#include <SDL_mixer.h>
#include <memory>

class SDL2_Music {
 public:
  bool loadMusic(const std::string& path);
  bool play(int loops = -1);
  // more methods
 private:
  std::unique_ptr<Mix_Music> music_ = nullptr; 
};

/* cpp file */
bool SDL2_Music::loadMusic(const std::string& path) {
  music_ = std::make_unique<Mix_Music>(Mix_LoadMUS(path.c_str()));
  if (music_ == nullptr) {
    ktp::logSDLError("Mix_LoadMUS");
    return false;
  }
  return true;
}
// more stuff

When I try to compile (GCC) I get:

"C:\Users\not_bjarne\CodeBlocks\MinGW\lib\gcc\x86_64-w64-mingw32\8.1.0\include\c++\bits\unique_ptr.h|831|error: invalid use of incomplete type 'struct _Mix_Music'"

And codeblocks points me to unique_ptr.h, which I obviously didn't tried to "fix".


Solution

  • It seems that Mix_Music is an incomplete type and the correct way to free a Mix_Music object is to call the Mix_FreeMusic function. You cannot dispose of it the way you would a C++ object, namely by using delete. delete would attempt to call the destructor (which cannot be done in this context, since the type is incomplete here) and would assume that the object was allocated by new and return the memory to the same pool where new got it from. The way SDL allocates the object is an implementation detail, so you must let SDL deallocate the object itself as well, to ensure it is done properly.

    std::unique_ptr can be used for this purpose, but requires a custom deleter. The default deleter will call delete, which should not be done here. The error you are seeing is because delete p; is ill-formed (where p has type Mix_Music*) because of the incompleteness. You must ensure that the custom deleter calls Mix_FreeMusic. You can see how to use custom deleters here: How do I use a custom deleter with a std::unique_ptr member?