SuomiGameHUB

Maailma tarvitsee pelejä

SDL2 ja älykkäät osoittimet

Kehittäessämme SDL2-pelimuottoriamme C++:n avulla, kohtaamme mielenkiintoisia haasteita, jotka juontavat juurensa kieleen itseensä ja sen kehitykseen. C++ on peräisin 1980-luvulta ja sen alkuperäinen tarkoitus oli laajentaa C-kieltä lisäämällä siihen luokat oliohjelmoinnin mahdollistamiseksi. Vuosien saatossa C++ on kuitenkin kasvanut huomattavasti ja sen standardiversiot ovat tuoneet mukanaan merkittäviä uudistuksia, kuten vektorit ja älykkäät osoittimet (smart pointers), jotka helpottavat muistinhallintaa.

C++11-standardi toi mukanaan älykkäät osoittimet, kuten std::shared_ptr ja std::unique_ptr, jotka ovat muuttaneet tapaa, jolla kehittäjät hallitsevat dynaamisesti varattua muistia. Nämä osoittimet auttavat automatisoimaan muistin vapauttamisen, vähentäen muistivuotojen riskiä ja parantaen ohjelmien luotettavuutta. Kuitenkin, kun työskentelemme SDL2:n (Simple DirectMedia Layer) kaltaisten C:llä kehitettyjen kirjastojen kanssa, kohtaamme haasteita yhdistäessämme C++:n modernit ominaisuudet C-pohjaiseen koodiin.

Muistinhallinnan kannalta, C-kielen rakenteissa, kuten SDL:n käyttämissä struktuureissa, ei ole konstruktoreita eikä destruktoreita, jotka ovat olennainen osa C++:n oliohjelmointia. Kun luomme objekteja C++:ssa, konstruktorit alustavat objektin tilan ja destruktorit huolehtivat resurssien vapauttamisesta. C:ssä vastaava toiminnallisuus saavutetaan manuaalisesti määrittelemällä funktioita, jotka alustavat ja vapauttavat resurssit. Tämän seurauksena, kun integroimme SDL:n kaltaisia kirjastoja C++-projekteihin, meidän on itse huolehdittava muistin allokoinnista ja deallokaatiosta tavalla, joka ei ole yhtä suoraviivaista kuin älykkäiden osoittimien käyttö.

Tässä yhteydessä meidän on oltava erityisen tarkkaavaisia muistinhallinnan suhteen. Vaikka C++ tarjoaa työkalut automaattiseen muistinhallintaan älykkäiden osoittimien muodossa, SDL:n käyttö edellyttää perinteisempää lähestymistapaa. Tämä tarkoittaa, että meidän on manuaalisesti varattava muisti SDL:n struktuureille ja vapautettava se asianmukaisesti ohjelman suorituksen lopussa. Tämä prosessi on kriittinen, sillä muuten riskinä on muistivuodot, jotka voivat kuluttaa kaiken käytettävissä olevan muistin ja johtaa ohjelman tai jopa koko järjestelmän kaatumiseen.

C++:n tarjoamat ratkaisut, kuten std::unique_ptr, tuovat mukanaan sekä tehokkuutta että automaatiota muistinhallintaan. std::unique_ptr on älykäs osoitin, joka omistaa ja hallinnoi toista objektia sen pointerin kautta ja tuhoaa objektin automaattisesti, kun osoitin poistuu alueestaan (scope). Tämä ominaisuus on erityisen hyödyllinen dynaamisesti varatun muistin kanssa työskenneltäessä, sillä se auttaa välttämään muistivuotoja ja tekee koodista turvallisempaa ja helpommin ylläpidettävää.

Kun unique_ptr määritellään tietyssä lohkossa, se varmistaa, että kun kyseinen lohko päättyy ja sen scope poistuu, osoittimen hallinnoima objekti tuhotaan automaattisesti. Miten voimme hyödyntää C++:n edistyneitä ominaisuuksia, kuten älykkäitä osoittimia, yhdessä C-kirjaston kanssa?

Ratkaisuksi tähän haasteeseen on kehitetty erilaisia tekniikoita. Esimerkiksi, voimme luoda mukautetun deletointi-strategian std::unique_ptrille, jotta se voi käsitellä C-tyylisiä resursseja, kuten SDL:n ikkunoita. Tässä lähestymistavassa luodaan mukautettu deleter-luokka, joka sisältää logiikan C-tyylisen resurssin vapauttamiseksi. Kun std::unique_ptr alustetaan käyttämään tätä mukautettua deleteria, se kutsuu automaattisesti määriteltyä vapautusfunktiota, kun osoitin poistuu alueestaan. Tämä integraatio tarjoaa siistin ja tehokkaan tavan hallita C-tyylisiä resursseja C++ -koodissa, säilyttäen samalla modernin C++:n turvallisuus- ja helppokäyttöisyysedut.

#include <SDL.h>
#include <memory>

struct SDLWindowDeleter {
  void operator()(SDL_Window* window) const {
    SDL_DestroyWindow(window);
  }
};

int main(int argc, char* argv[])
{
  std::unique_ptr<SDL_Window, SDL_WindowDeleter> window(
        SDL_CreateWindow("SGH SDL Esimerkki", 
                         0, 0, 
                         1280, 720, 
                         SDL_WINDOW_SHOWN));

  SDL_Surface* screenSurface = NULL;
  screenSurface = SDL_GetWindowSurface(window.get());
  SDL_FillRect(screenSurface, NULL, SDL_MapRGB(screenSurface->format, 0xFF, 0xFF, 0xFF));
  SDL_UpdateWindowSurface(window.get());

  SDL_Event e;
  bool quit = false;

  while (quit == false)
  {
    while (SDL_PollEvent(&e))
    {
      if (e.type == SDL_QUIT) quit = true;
    }
  }

  SDL_Quit();

  return 0;
}

Edellinen osa: SDL2 Ikkuna