C++ wrapper for font opaque pointer
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