Pull to refresh

Comments 32

Есть большущий минус, если захочется локализовать приложение, то такой вариант никак не подойдет. Для этого и придумано семейство printf, с другой стороны С++ не позволяет указать порядок. А вот boost::format это делает с легкостью, многие почему-то боятся boost, но данная библиотека очень сильно экономит время при разработке ПО.
Классно! Спасибо за наводку на boost::format.

#include <boost/format.hpp>

class MakeString2 {
public:
    MakeString2(const char* fmt): m_fmt(fmt) {}

    template<class T>
    MakeString2& operator<< (const T& arg) {
        m_fmt % arg;
        return *this;
    }
    operator std::string() const {
        return m_fmt.str();
    }
protected:
    boost::format m_fmt;
};


void func(int id, const std::string& data1, const std::string& data2)
{
    throw std::runtime_error(MakeString2("Operation with id = %1% failed, because data1 (%2%) is incompatible with data2 (%3%)") << id << data1 << data2);
}
Более того, аналогичная описанной в статье обёртка над std::stringstream находится где-то глубоко внутри boost::lexical_cast.
Вертно, только для базовых типов там есть оптимизация, которая делает преобразование без использования stringstream.
Могут возникнуть проблемы при форматированном выводе дробных чисел, указателей и т.д.
Имхо, printf более конкретен.
А iomanip не спасает, разве?
В С++ не бум-бум, но недавно увидел, что с помощью этой библиотеки удачно делаь строку на подстроку через делитель. Может кто-либо подсказать более изящнее решение на отсутствие таких стандартных функций как split; explode etc.?
boost::tokenizer или boost::split из набора строковых алгоритмов, недостающих в C++ (boost string algo): www.boost.org/doc/libs/1_47_0/doc/html/string_algo.html

Первый мне не понравился, в нем нельзя разделители предикатом задавать (мне нужно было ::std::is_punct), а он умеет только массив символов принимаь.

Со вторым все ок.

При работе с вектором, например, очень удобно можно использовать ostream_iterator
Года 3 назад, тоже такую же обертку где-то у себя в проектах делал. Проблем не возникло.
Use boost::lexical_cast, Luke!
Он на порядок(!) быстрее стрингстрима за счет того, что не требует создания тяжелого объекта. Если не учитывать создание объекта, то он все равно быстрее, хотя уже на десятки процентов.

Сравнение внизу страницы: www.boost.org/doc/libs/1_47_0/libs/conversion/lexical_cast.htm
Изначально lexical_cast так и работал, со временем для стандартных типов добавили частную спецификацию.
Месяцем раньше и я бы вас расцеловал :)
Вот сэкономьте поцелуи, пробегитесь за пару дней по документации boost, хотя бы просто по общему описанию всех библиотек, чтобы знать когда куда копать.

Не тратьте поцелуи зря)
А есть ли в boost что-нибудь похожее на MakeString или MakeString2?
boost::lexical_cast(«123»);
boost::lexical_cast(123);
А как использовать boost::lexical_cast для форматирования строки?

Будьте добры, перепишите мой пример с выбрасыванием исключения с использованием boost::lexical_cast.
void func(int id, const std::string& data1, const std::string& data2)
{
    throw std::runtime_error("Operation with id = " + boost::lexical_cast<std::string>(id) + " failed, because data1 (" + data1 + ") is incompatible with data2 (" + data2 + ")");
}
Угу. Только нужно первый строковый операнд ещё принудительно привести к std::string, т.к. для const char* не определен оператор "+".

Но я впрочем, не об этом. Положим, data1 и data2 имеют сложный тип (пусть это будут экземпляры классов A и B). Тогда имеем:

void func(int id, const A& data1, const B& data2)
{
    throw std::runtime_error(std::string("Operation with id = ") + boost::lexical_cast<std::string>(id) + " failed, because data1 (" + boost::lexical_cast<std::string>(data1) + ") is incompatible with data2 (" + boost::lexical_cast<std::string>(data2) + ")");
}


Это сработает, если для A и B определены операторы вывода в поток (<<).

А теперь сравните то же самое с использованием предлагаемого класса MakeString:
void func(int id, const A& data1, const B& data2)
{
    throw std::runtime_error(MakeString() << "Operation with id = " << id << " failed, because data1 (" << data1 << ") is incompatible with data2 (" << data2 << ")");
}


По-моему, получилось значительно компактнее, разве нет?

Я просто хочу подчеркнуть, что целью данного топика было представить класс MakeString() в контексте его использования для форматирования строк, а не использование std::stringstream для преобразования типов.

MakeString() позволяет форматировать строку, как если бы она была потоком.

MakeString — 'inplace' конвертер из std::ostream в std::string. Вот в чем суть. Наверное, стоит добавить эти разъяснения в топик.
Угу. Только нужно первый строковый операнд ещё принудительно привести к std::string, т.к. для const char* не определен оператор "+".
Не нужно, я же проверил на компилябельность перед отправкой комментария.

Ну а так, для составления сложных строк уже придуман упомянутый выше boost::format, его можно научить и с пользовательскими типами работать.
Не нужно, я же проверил на компилябельность перед отправкой комментария.

А я поленился :)

Ну а так, для составления сложных строк уже придуман упомянутый выше boost::format, его можно научить и с пользовательскими типами работать.


Беда в том, что boost::format, как выяснилось, тоже не умеет автоматически преобразовываться к строке:

std::cout << boost::format("%2% - %1%") % 1 % 2 << std::endl; // работает

...

boost::format fmt("%2% - %1%");
fmt  % 1 % 2;
std::string str = fmt.str() // работает

...

boost::format fmt("%2% - %1%");
std::string str = fmt  % 1 % 2; // не работает

...

std::string str = boost::format("%2% - %1%")  % 1 % 2; // не работает

...

throw std::runtime_error(boost::format("%2% - %1%")  % 1 % 2); // не работает

...

std::string str = MakeString2("%2% - %1%") << 1 << 2; // работает (MakeString2 определен в моем комментарии выше)
using boost::str;

throw std::runtime_error(str(boost::format("%2% — %1%") % 1 % 2));

И велосипед не нужен=)
Во! Спасибо. Этого я и добивался от f0b0s.
Да, классы хорошие. С их помощью очень удобно делать логгер, который понимает любые типы, которые умеют сериализоваться в std::ostream

Например так:
#include <string>
#include <iostream>
#include <sstream>
using namespace std;

class LogMessage
{
public:
	~LogMessage()
	{
		cout << msg << endl;
	}
	
	static LogMessage log()
	{
		return LogMessage();
	}

	template<class T>
	LogMessage& operator<<(const T& obj)
	{
		std::ostringstream ostr;
		ostr << obj;
		if(!msg.empty())
			msg+=" ";
		msg+=ostr.str();
		return *this;
	}
	
private:
	LogMessage(){};
	std::string msg;
};

LogMessage log()
{
	return LogMessage::log();
}


struct Custom
{
	Custom():x(33), name("some_obj"){}
	
	int x;
	std::string name;
};

std::ostream& operator<< (std::ostream& ostr, const Custom& obj)
{
	return ostr << "x="<<obj.x<<", name: " << obj.name;
}

int main()
{
	log() << "hey" << 33;
	
	Custom customObject;
	customObject.x = 37;
	
	log() << 3.4 << " and " << customObject;
}
Боже, зачем вы каждый раз создаете ::std::ostringstream, тем более, если это метод, а не свободная функция?

Ну сделайте вы его членом, да очищайте через ostream.str(::std::string()).

Тому, кто скажет, что это преждевременная оптимизация, я отвечу, что не сделать этого — преждевременная пессимизация, так как перенести 1 строку легко, а искать почему с логами тормозит, а без них нет профилировщиком — бесполезная трата времени получится.
Есть принцип — создавать объекты в наименьшей области видимости. Это говорит компилятору о том, что где нужно, и дальше он сам уже может оптимизировать.
Насчет данного примера — логи тормозят в момент вывода на консоль или в момент записи в лог файл. Поэтому иногда приходится делать их асинхронными, но вот создание временных объекто не тормозило ни разу.

PS. Если вам ну так сильно хочется оптимизровать код — то можно сразу же заменить код фукнции на boost::lexical_cast — он в разы быстрее, и точней отражает сущность нужного нам преобразования.
UFO just landed and posted this here
Если вы так боитесь преждевременной пессимизации в связи с созданием временных переменных, то предлагаю при старте программы выделять массив данных, и потом все переменные использовать как элементы этого массива.
Вы можете шуть сколько угодно, но я натыкался на горло, когда создание стрингстрима тормозило. Конкретно он — тяжелый объект.

Касательно компилятора — оптимизаторы значительно умнее, чем вы, вероятно, думаете, и по CFG спокойно можно понять, где что используется и без областей видимостей, не говорите глупостей.
в качестве альтернативы хитрожопому классу с неявным преобразованием к std::string могу предложить хитрожопый макрос, не использования ради, а забавы для.
#define STR(WHAT) ({std::stringstream e;e<<WHAT;e.str();})

использование:
throw runtime_error(STR("2+2="<<5));
А такая же статья только форматированный ввод есть?
Sign up to leave a comment.

Articles

Change theme settings