C++ wrapper for font opaque pointer I'm creating a class which has a raw pointer member which comes from a C function. In order to make it RAII approved, I just created a method free() that is called on the destructor of this class.
Reading some guidelines I came across that Rule of 5 (and Rule of 3) which, from my understanding, I should also create a bunch of constructors and operator overloadings.
This is what I've done:
/* header */
class SDL2_Font {
 public:
  SDL2_Font() {}
  SDL2_Font(const SDL2_Font&amp; other);  // copy constructor
  SDL2_Font(SDL2_Font&amp;&amp; other);       // move constructor
  ~SDL2_Font() { free(); }

  SDL2_Font&amp; operator=(const SDL2_Font&amp; other); // copy assignment
  SDL2_Font&amp; operator=(SDL2_Font&amp;&amp; other);      // move assignment
 
  bool loadFont(const std::string&amp; path, int size);
  // more stuff
 private:
  void free();

  TTF_Font* font_ = nullptr;
  FontInfo font_info_ {};
  std::string path_ {};
  int size_ = 0;
}


/* cpp file */
SDL2_Font::SDL2_Font(const SDL2_Font&amp; other) {
  if (other.font_ != nullptr) {
    SDL2_Font::loadFont(other.path_, other.size_);
  }
}

SDL2_Font::SDL2_Font(SDL2_Font&amp;&amp; other): font_{other.font_} {
  other.font_ = nullptr;
}

SDL2_Font&amp; SDL2_Font::operator=(const SDL2_Font&amp; other) {
  if (&amp;other != this &amp;&amp; other.font_ != nullptr) {
    SDL2_Font::loadFont(other.path_, other.size_);
  }
  return *this;
}

SDL2_Font&amp; SDL2_Font::operator=(SDL2_Font&amp;&amp; other) {
  if (&amp;other != this) {
    SDL2_Font::free();
    font_ = other.font_;
    other.font_ = nullptr;
  }
  return *this;
}

void SDL2_Font::free() {
  if (font_ != nullptr &amp;&amp; TTF_WasInit()) {
    TTF_CloseFont(font_);
    font_ = nullptr;
    path_.clear();
    size_ = 0;
    font_info_ = {};
  }
}

bool SDL2_Font::loadFont(const std::string&amp; path, int size) {
  SDL2_Font::free();
  font_ = TTF_OpenFont(path.c_str(), size);
  if (font_ == nullptr) {
    ktp::logSDLError(&quot;TTF_OpenFont&quot;);
    return false;
  }
  path_ = path;
  size_ = size;
  SDL2_Font::queryFontInfo();
  return true;
}

This works, or at least I looks to me. But because it's my first time doing this stuff I just want to somebody take a look at it. What do you think? Is the way I implemented the methods/overloads correct?
Or maybe it's a bit overkill? Because I don't intend to copy/move/assign SDL2_Fonts and just for that destructor I had to do all those methods.
Because you

don't intend to copy/move/assign SDL2_Fonts

you could formalize that with
SDL2_Font(const SDL2_Font&amp; other) = delete;
SDL2_Font&amp; operator=(const SDL2_Font&amp; other) = delete;

(the move versions of these operations won't be generated if these two are defined or deleted).
However, let's assume that one day we will want to be able to pass these fonts by value.
The code looks correct to my eye, but you could avoid having to do the memory management yourself if you use a smart pointer instead of a raw pointer for the font_ member.  If you do that, you can follow the Rule of Zero (the best of these rules) and simply let the compiler create the appropriate copy operators and constructors - see worked code below.
I notice that free() can only ever be called from the destructor. (Edit - I see it's also called from loadFont() but less obviously so, by being class-qualified.  This paragraph still applies, though.) free() does a lot of &quot;dead writes&quot; most times it's called - i.e. making assignments to variables that can never be read.  That's a waste of your time and mine!  Possibly also of your processor's, though a decent compiler should be able to optimise most of that away.

Here's a simplified version using shared pointer (I'm assuming that copies don't each need their own font object, as we don't ever modify it):
#include &lt;SDL.h&gt;
#include &lt;SDL_ttf.h&gt;

#include &lt;memory&gt;
#include &lt;string&gt;

namespace ktp {
    void logSDLError(const char*);
}

/* header */
class SDL2_Font {
public:
    SDL2_Font() {}

    bool loadFont(const std::string&amp; path, int size);
private:
    struct FontInfo {};
    void queryFontInfo();

    std::shared_ptr&lt;const TTF_Font&gt; font_{};
    FontInfo font_info_ {};
    std::string path_ {};
    int size_ = 0;
};


// look, no constructors or assignment operators!

bool SDL2_Font::loadFont(const std::string&amp; path, int size)
{
    font_.reset(TTF_OpenFont(path.c_str(), size),
                &amp;TTF_CloseFont);
    if (!font_) {
        ktp::logSDLError(&quot;TTF_OpenFont&quot;);
        font_info_ = {};
        path_ = {};
        size_ = 0;
        return false;
    }
    path_ = path;
    size_ = size;
    queryFontInfo();
    return true;
}

See how reset() takes care of releasing resources for us, and we don't need to write a destructor.
I tried this with a simple main():
int main()
{
    if (TTF_Init()) {
        std::cerr &lt;&lt; &quot;TTF_Init: &quot; &lt;&lt; TTF_GetError() &lt;&lt; '\n';
        return 1;
    }

    {
        SDL2_Font f;
        f.loadFont(&quot;/usr/share/fonts/truetype/unifont/unifont.ttf&quot;, 14);
        f.loadFont(&quot;/usr/share/fonts/truetype/unifont/unifont.ttf&quot;, 16);
        // important - f goes out of scope before TTF_Quit()
    }

    TTF_Quit();
}

This runs cleanly under Valgrind:
==29172== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==29172== Command: ./255607
==29172== 
==29172== 
==29172== HEAP SUMMARY:
==29172==     in use at exit: 0 bytes in 0 blocks
==29172==   total heap usage: 155 allocs, 155 frees, 615,577 bytes allocated
==29172== 
==29172== All heap blocks were freed -- no leaks are possible