How can I write a function like prinft to buid a log class?

Started by
49 comments, last by Zahlman 15 years, 11 months ago
Hi mates! I would like to create a log class with a method like this: m_log->Write_log("example number %d", 1); What I don't know is how to create/program arguments like printf. Thank you for your help. BTW, using Code::Blocks and C++
I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhauser gate. All those moments will be lost in time, like tears in rain. Time to die.
Advertisement
You'd use the va_args API -- va_start, va_end, and v*printf() methods. However, this is generally considered poor practice in C++ because of the extreme lack of type safety and otherwise brittle nature of the varargs methods.

A better solution is to overload some other operator -- such as <<, as the iostreams do. Other common options are operator% and operator(). Or simply using Boost.Format.

You'd end up with something like
LogScope log;log.Write() << foo << bar << baz;
http://en.wikipedia.org/wiki/Stdarg.h
printf is a variadic function. Writing them requires using the va_args facility of C, and it's important to be familiar with it, however don't do this.

If you're writing C++, support operating chaining instead, like C++'s I/O streams. Instead of
lobObject.write("example number %d", 1);
do
logObject << "example number " << 1;

Implementing this requires that your insertion operator (here we use operator <<, just as with C++ streams) return a reference to the log object. You will need to provide overloads for each insertable type:
Log & operator << (Log & log, int i){ ... }Log & operator << (Log & log, float f){ ... }Log & operator << (Log & log, char c){ ... }Log & operator << (Log & log, const std::string & s){ ... }
Here's what I use:

ReturnCode CLog::Log(LogType Type, const char *Format, ...){	va_list	Args;	char	String[256];	if (FALSE == Logging && Error == Type)	{		Logging	= TRUE;	}	va_start(Args, Format);#if _MSC_VER >= 1400	vsprintf_s(String, Format, Args);#else	vsprintf(String, Format, Args);#endif	va_end(Args);	return	LogString(Type, String);}ReturnCode CLog::LogString(LogType Type, string NewString){#ifdef _DEBUG	OutputDebugString(NewString.c_str());	OutputDebugString("\n");#endif	LogDateTime();	switch (Type)	{		case Error:			LogStrings.push_back("<FONT COLOR=#FF0000>Error: ");			break;		case Warning:			LogStrings.push_back("<FONT COLOR=#FFFF00>Warning: ");			break;		case Message:		default:			LogStrings.push_back("<FONT COLOR=#00FF00>");			break;	}	LogStrings.push_back(NewString);	LogStrings.push_back("</FONT>");	return	NoError;}ReturnCode CLog::LogDateTime(){	char	String[256];	LogStrings.push_back("<FONT COLOR=#FFFFFF>[");#ifdef LOGDATE	GetDateFormat(LOCALE_USER_DEFAULT, 0, NULL, "ddd',' MMM dd yy", String,	sizeof(String));	LogStrings.push_back(String);	LogStrings.push_back(" ");#endif	GetTimeFormat(LOCALE_USER_DEFAULT, 0, NULL, "hh':'mm':'ss tt", String, sizeof(String));	LogStrings.push_back(String);	LogStrings.push_back("]</FONT> ");	return	NoError;}

Check out Super Play, the SNES inspired Game Engine: http://www.superplay.info

Quote:Original post by jpetrie
You'd use the va_args API -- va_start, va_end, and v*printf() methods. However, this is generally considered poor practice in C++ because of the extreme lack of type safety and otherwise brittle nature of the varargs methods.


That is generally not true, at least with GCC, which gives you warnings when you pass an argument with mismatched type to printf-like functions. If you want to use it with your own procedures then you need to apply an additional attribute

Personally, I find C++ style printing functions unreadable and encourage the use of printf-like. (Since as I said, it's type unsafety is generally untrue, and it's more convenient; KISS)

[edit]
Compare the readability:

std :: cout << "Hello " << first_name << " " << last_name << ".";
printf( "Hello %s %s.", first_name, last_name );

The second one is definitely more readable; besides, it's less to type.
[/edit]
Quote:Original post by desudesu
Quote:Original post by jpetrie
You'd use the va_args API -- va_start, va_end, and v*printf() methods. However, this is generally considered poor practice in C++ because of the extreme lack of type safety and otherwise brittle nature of the varargs methods.

That is generally not true, at least with GCC, which gives you warnings when you pass an argument with mismatched type to printf-like functions.

Warnings on mismatched type are not the same thing as strong type validation.

Quote:Compare the readability:

std :: cout << "Hello " << first_name << " " << last_name << ".";
printf( "Hello %s %s.", first_name, last_name );

The second one is definitely more readable; besides, it's less to type.

It's less secure, though. Boost.Format is a compromise:
cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50;      // prints "writing toto,  x=40.230 : 50-th try"
Quote:Original post by Oluseyi
Quote:Original post by desudesu
Quote:Original post by jpetrie
You'd use the va_args API -- va_start, va_end, and v*printf() methods. However, this is generally considered poor practice in C++ because of the extreme lack of type safety and otherwise brittle nature of the varargs methods.

That is generally not true, at least with GCC, which gives you warnings when you pass an argument with mismatched type to printf-like functions.

Warnings on mismatched type are not the same thing as strong type validation.

Quote:Compare the readability:

std :: cout << "Hello " << first_name << " " << last_name << ".";
printf( "Hello %s %s.", first_name, last_name );

The second one is definitely more readable; besides, it's less to type.

It's less secure, though. Boost.Format is a compromise:
cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50;      // prints "writing toto,  x=40.230 : 50-th try"


If you want then you can tick GCC's flag to treat these warnings as errors. Unless I'm missing something obvious, I don't see any security defect with printf-like functions, provided you will treat these warnings as errors. Why go the hard way when the easy one is as good? (More dependencies, more complexity and longer compile-times are generally a bad thing if they don't contribute something obviously useful.)
Quote:
The second one is definitely more readable; besides, it's less to type.

Wrong. Readability is fairly subjective (you said it yourself -- 'in [your] opinion'), so the assertion is off-base (I find them equally readable, for example).

Quote:
(Since as I said, it's type unsafety is generally untrue,

Wrong, see above. Warnings are not type safety.

Quote:
Why go the hard way when the easy one is as good?

Because it isn't as good. Warnings on a specific compiler about a potential mismatch to not constitute type safety. Type safety is a language-level feature, and here the language abandons you.

The operator chaining concept is the standard, idiomatic way of doing this in C++ and is vastly more useful and beneficial than the vararg idiom of C. If nothing else, it lets you put (non-POD) objects directly into the format stream... something that is flat out impossible with vararg functions.

The implementation effort is relatively similar -- you don't have to manually overload the operator for each type, for example, you can use a template method.

Quote:
More dependencies, more complexity and longer compile-times are generally a bad thing if they don't contribute something obviously useful.)

Implementing varags requires you depend on at least one other header for va_arg and the like. Implementing operator chaining yourself requires no additional headers. I don't know what kind of machine you're using, but on my machine single templated operator<< doesn't even make a dent in my compile time. And I fail to see how you think actual type-safety and the ability to use (non-POD) objects as first-class parameters (like std::string) is not 'obviously useful.'
Quote:Original post by jpetrie
Quote:
(Since as I said, it's type unsafety is generally untrue,

Wrong, see above. Warnings are not type safety.

You can turn them into errors with appropriate flag. I don't see how this type-safety is different from that type-safety.

Quote:
Quote:
More dependencies, more complexity and longer compile-times are generally a bad thing if they don't contribute something obviously useful.)

Implementing varags requires you depend on at least one other header for va_arg and the like. Implementing operator chaining yourself requires no additional headers. I don't know what kind of machine you're using, but on my machine single templated operator<< doesn't even make a dent in my compile time. And I fail to see how you think actual type-safety and the ability to use objects as first-class parameters (like std::string) is not 'obviously useful.'


I was talking about Boost.format, which is an overkill for me personally, but then, YMMV.

This topic is closed to new replies.

Advertisement