Kirjautuminen

Haku

Tehtävät

Koodit: C++: Tekstin muotoilu string-olioon

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;
}

Kommentit

eq [28.12.2010 14:14:01]

#

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 deleten 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)

Metabolix [29.12.2010 17:37:37]

#

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.

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta