c++codeblockssdl-2sdl-ttf

TTF_RenderText_Solid(...) causes segmentation fault error


I'm trying to utilize a class Text to handle with text in my c++ SDL2-based program.

TTF_RenderText_Solid works perfectly in the main function, although in my classe Text, in this line here SDL_Surface *surface = TTF_RenderText_Solid( font, text.c_str(), fg );, it's causing some errors. Sometimes it gives me a segmentation fault error, sometimes it didn't.

I debugged the code and all the three variables, TTF_Font *font, std::string text and SDL_Color fg, were with theirs respective correct values.

My main function:

#include <iostream>

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>

#include "Text.h"

int main()
{
    try
    {
        SDL_Init( SDL_INIT_VIDEO );
        TTF_Init();

        SDL_Window *window = SDL_CreateWindow( "Window", SDL_WINDOWPOS_CENTERED, 
        SDL_WINDOWPOS_CENTERED, 800, 800, SDL_WINDOW_SHOWN );
        SDL_Renderer *renderer = SDL_CreateRenderer( window, -1, SDL_RENDERER_ACCELERATED );


        TTF_Font *f = TTF_OpenFont( "cruft.ttf", 32 );
        SDL_Surface *s = TTF_RenderText_Solid( f, "Avocado", {0,0,0,255} );
        if(s == NULL)
            std::cout << "s == NULL\n";



        Text title;
        title = Text( renderer, "cruft.ttf", 32, "title" );
        title.render_Text_Solid( "Avocado", {0,0,0,255} );



        SDL_Quit();
        TTF_Quit();

        return 0;
    }
    catch( std::exception& e )
    {
        std::cerr << "Error: " << e.what() << "\n";
        return 0;
    }
    catch(...)
    {
        std::cerr << "Unkown error!\n";
        return 0;
    }
}

My Text.cpp file:

#include <iostream>
#include <string.h>

#include "Text.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>

//Constructors
Text::Text(){}

Text::Text(SDL_Renderer *renderer, std::string file, int ptsize, std::string name)
{
    set_renderer( renderer );
    this->name = name;

    set_TTF_Font( file, ptsize );
}

Text::~Text()
{
    TTF_CloseFont( font );
    SDL_DestroyTexture( texture );
}



void Text::set_renderer( SDL_Renderer *renderer )
{
    if( renderer == NULL )
        throw std::runtime_error( name + ": Renderer could not be set! renderer == NULL\n" + SDL_GetError() );

    this->renderer = renderer;
}

void Text::set_TTF_Font( std::string file, int ptsize )
{
    TTF_CloseFont( font );
    font = NULL;
    SDL_DestroyTexture( texture );
    texture = NULL;
    width = 0;
    height = 0;

    if( file == "" )
        throw std::runtime_error( name + ": TTF_Font* could not be set! file == ""\n" + SDL_GetError() );
    if( ptsize <= 0 )
        throw std::runtime_error( name + ": TTF_Font* could not be set! ptsize <= 0\n" + SDL_GetError() );

    TTF_Font *font = TTF_OpenFont( file.c_str(), ptsize );

    if( font == NULL )
        throw std::runtime_error( name + ": TTF_Font* could not be set! font == NULL\n" + SDL_GetError() );

    this->font = font;
}

void Text::render_Text_Solid( std::string text, SDL_Color fg )
{
    SDL_DestroyTexture( texture );
    texture = NULL;
    width = 0;
    height = 0;

    SDL_Surface *surface = TTF_RenderText_Solid( font, text.c_str(), fg );

    if( surface == NULL )
        throw std::runtime_error( name + ": Text could not be created! surface == NULL\n" + SDL_GetError() );

    texture = SDL_CreateTextureFromSurface( renderer, surface );
    width = surface->w;
    height = surface->h;
    SDL_FreeSurface( surface );

    if( texture == NULL )
        throw std::runtime_error( name + ": Text could not be created! texture == NULL\n" + SDL_GetError() );
}

My Text.h file:

#ifndef TEXT_H_INCLUDED
#define TEXT_H_INCLUDED

#include <iostream>

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>

class Text
{
    public:

    //constructors
    Text();
    Text(SDL_Renderer *renderer, std::string file, int ptsize, std::string name);
    ~Text();

    void set_renderer( SDL_Renderer *renderer );
    void set_TTF_Font( std::string file, int ptsize );
    void render_Text_Solid( std::string text, SDL_Color fg );

    //render
    void render( int x, int y );
    void render( SDL_Rect *srcrect, SDL_Rect *dstrect );

    //variables
    int width = 0;
    int height = 0;

    private:
    SDL_Renderer *renderer = NULL;
    SDL_Texture *texture = NULL;
    TTF_Font *font = NULL;
    std::string name = "";

};

#endif // TEXT_H_INCLUDED

PS: I utilizing Manjaro Linux and Codeblocks


Solution

  • Let's first look at what these lines do:

        Text title;
        title = Text( renderer, "cruft.ttf", 32, "title" );
    

    First you create title, and initialise it with default constructor Text::Text(), which sets default values to its fields. Then you create a second Text (let's call it title1 for clarity) object with specialised constructor. Then you copy title1 to title - since no operator= is defined, default copy is generated. Now your title have the same values as title1. And title1.~Text() is invoked, killing your font.

    What you have is your title.font still having its previous value, but it points to already closed font structure.

    That could be avoided by not constructing temporary Text object - e.g. Text title = Text(.......) will not produce temporary object and copying. That's just a workaround, actual problem remains - your Text objects cannot be safely copied. There are many ways to approach this problem - like using some sort of unique_ptr wrappers or dropping destructors in favor of manual deinit method, etc..

    Which brings us to next problem - if you solve your copying, you now can generate text surface and texture, but look at your exit code:

        Text title;
        // ... omitted
        SDL_Quit();
        TTF_Quit();
        return 0;
    

    You have your finalisation in reverse - you want to close font, drop textures, destroy renderer/window, then call TTF_Quit and SDL_Quit, but instead you have SDL_Quit(), TTF_Quit(), and only after that title.~Text() - will'll probably crash because SDL/TTF already finalised and you're not supposed to perform SDL calls after that. Of course that could also be solved, e.g. by enclosing your title into extra code block, but the amount of things to watch for becoming too large.