Kirjoittaja: Metabolix
Kirjoitettu: 06.02.2010 – 06.02.2010
Tagit: teksti, koodi näytille, vinkki
Usein on tarpeen tulostaa muuttujien arvoja string-olioon esimerkiksi virheilmoituksia varten, mutta tämä on C++:n valmiilla välineillä melko vaivalloista. Siksi kirjoitin C:n sprintf-funktiota vastaavan C++-funktion, joka muodostaa annetuista muuttujista tekstiä määrätyn formaatin mukaan. Funktio itse asiassa käyttää C:n vsnprintf-funktiota ja tukee siis täsmälleen samoja formaatteja ja muuttujatyyppejä.
Alla on ensiksi otsikkotiedosto, jossa funktiot esitellään, ja sitten kooditiedosto, jossa funktiot toteutetaan. Selkeyden vuoksi funktiosta on sekä vapaita argumentteja ottava versio että valmiin argumenttilistan ottava versio. Käytännössä näistä ensin mainittu (strprintf) riittää ohjelmoijalle.
Lopussa on vielä esimerkki, jossa muodostetaan strprintf-funktiolla yksinkertainen teksti virheilmoitusta varten.
strprintf.hpp
#ifndef _STRPRINTF_HPP #define _STRPRINTF_HPP 1 #include <cstdarg> #include <string> std::string vstrprintf(const char* fmt, va_list args); std::string strprintf(const char* fmt, ...); #endif
strprintf.cpp
#include "strprintf.hpp" #include <string> #include <vector> #include <cstdarg> #include <cstdio> // vstrprintf: tulostetaan valmiin argumenttilistan muuttujat. std::string vstrprintf(const char* fmt, va_list args) { // Optimointi: varataan oletuksena jonkinlainen puskuri. char minibuf[128]; // Luodaan teksti puskuriin; funktio kertoo tarvittavan tilan. int len = vsnprintf(minibuf, sizeof(minibuf), fmt, args); // Jos tarvittava tila on puskurin kokoa pienempi, teksti mahtui // ja puskurin sisältö voidaan palauttaa saman tien. if (len < (int) sizeof(minibuf)) { return minibuf; } // Muuten varataan tekstille isompi puskuri ja luodaan siihen // täysimittainen teksti, joka sitten kopioidaan string-olioon. std::vector<char> buf(len + 1); vsnprintf(&buf[0], len + 1, fmt, args); std::string ret = &buf[0]; return ret; } // strprintf: avataan argumenttilista ja kutsutaan vstrprintf-funktiota. std::string strprintf(const char* fmt, ...) { // Avataan lista, kutsutaan funktiota, suljetaan lista, palautetaan. va_list args; va_start(args, fmt); std::string ret = vstrprintf(fmt, args); va_end(args); return ret; }
esimerkki.cpp
#include <iostream> #include <stdexcept> #include "strprintf.hpp" int main() try { throw std::runtime_error(strprintf("Virhe: luku = %d, x = %f, teksti = '%s'", 10, 3.5, "hei")); } catch (std::exception& e) { std::cout << e.what() << std::endl; }
Myöhäisiä kommentteja (eli kritiikkiä; näinhän täällä käy):
Aloittelevalle C++-ohjelmoijalle on tietysti hyvä vähän joka välissä muistuttaa että jokaista new[]
-operaatiota kohden tulee jossain olla sitä vastaava delete[]
(ei delete
!). Toisaalta, koko lailla yhtä tärkeää on muistuttaa ettei kaikkea tarvitse tehdä "manuaalisesti", kun tarjolla on usein parempi ratkaisu standardikirjaston muodossa.
Mitä käy, jos string
-luokan konstruktori heittää poikkeuksen new
:n ja delete
n välissä? (Valistunut arvaus: ohjelma kaatuu, koska C++-ohjelmoijilla ei ole tapana kerätä niitä poikkeuksia joita he eivät itse heitä. Jos ohjelmoija sattuu kuitenkin nappaamaan poikkeuksen, ei muistivuotoa voi millään välttää.) Vaaranpaikoista olisi hyvä vähintään kommentilla muistuttaa, jotta koodivinkkien lukijalle kehittyisi taito ne tulevaisuudessa itsekin löytää.
"Nokkela", mutta ei-alustariippumaton tapa olisi käyttää itse merkkijonoa kuten merkkijonopuskuria: luoda tietynpituinen string
-olio ja käyttää osoitinta sen ensimmäiseen alkioon kuin erillistä puskuria. Standardi ei kuitenkaan velvoita string-luokan sisäisen puskurin olevan jatkuva, joten kaikilla teoreettisilla toteutuksilla tämä keino ei toimi.
Parempi tapa onkin käyttää vector
-luokkaa kaikkiin dynaamisiin puskuritarpeisiin. Käytännössä vector
sisäisesti tekee juurikin saman, kuin mitä yllä tehdään "manuaalisesti", mutta paremmin: muistin varaamisen lisäksi vector
myös hallinnoi sitä, eli huolehtii muistin vapauttamisesta, myös poikkeustilanteissa.
(Havaitsin vielä lopuksi, että koodivinkki ei pysy yhtenäisenä sen suhteen, mihin asteriski osoitinmerkintöjen kanssa sijoitetaan)
eq kirjoitti:
Mitä käy, jos string-luokan konstruktori heittää poikkeuksen new:n ja deleten välissä?
Hyvä huomio, nyt koodi käyttää puskurina std::vector-luokkaa. Toki on todennäköistä, että std::string ajaisi saman asian, vaikka standardi ei näin lupaakaan.