1

I am trying to modify my log class to accept variables in my string. For example, if I wanted to output that there are 7 players in an area.

Here is my write to log function:

void Log::writeSuccess(string text,...)
{
    // Write the sucessfull operation to the logfile
    logfile << "<---> " << text << endl;
}

And here is my calling code:

int playernum = 7;

errorLog.writeSuccess("There are %i players in the area", playernum);

It just ends up outputting to the file: There are %i players in the area

Any way to fix this?

sbi
  • 219,715
  • 46
  • 258
  • 445
  • 2
    I'm surprised this doesn't cause a compiler error, since you are passing the wrong number of arguments. The answer to your question is probably boost.log or boost.format, or perhaps variadic templates if you have C++0x. – Kerrek SB Jul 27 '11 at 11:30
  • 2
    You expected magic to happen? – R. Martinho Fernandes Jul 27 '11 at 11:31
  • are you after some kind of variadic function like printf? – sergio Jul 27 '11 at 11:31
  • Sorry, I edited my original post. Was playing around with it before I posted here. – Publeus Grant Jul 27 '11 at 11:33
  • 3
    This is a logging library and you're passing `std::string` _by value?_ – sbi Jul 27 '11 at 12:02
  • 1
    @sbi instead of just acting surprised, explain to me please why this is unwise... – Publeus Grant Jul 27 '11 at 12:13
  • @Publeus: You're invoking a string copy operation every time you call that function. Why? Why aren't you passing per `const` reference? I have once answered with a set of guidelines for [how to pass objects to function in C++](http://stackoverflow.com/questions/2139224/how-to-pass-objects-to-functions-in-c/2139254#2139254). – sbi Jul 27 '11 at 13:09
  • 2
    @Publeus: No offense meant, but why are you tasked with doing this when you don't know about passing arguments? Is this homework? A job? A hobby project? – sbi Jul 27 '11 at 13:10

5 Answers5

6

I wonder how on earth does your program even compile?! You call writeSuccess with 2 arguments, whereas it is declared to take only one argument.

You should look at boost format

Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
2

The problem with using printf-style format strings is that those strings are

  1. dependent on the types of the provided arguments, and
  2. dependent on the order of the provided arguments.

Not only is this error-prone when you are writing those lines. In my experience the types and order of the arguments will easily change in software that is actively maintained and extended, and it's much harder still to keep the format strings in sync with changes applied later, than it is to do so when you initially write the code.

The problem of needing to manually keep the parameter types in sync with the format string can easily be solved in C++, streams have proven that 25 years ago. Boost.Format even manages to combine format strings with type safety.

A different approach, solving both problems, is taken by some logging libraries I have seen: They use a syntax where you specify which parameter is to be inserted at a specific place in a string by using the parameter's name, and they free you from having to think about the parameter's type by individually converting all parameters to strings before inserting them:

log( "i now has the value of @(i), current size is @(x.get_size(y))", 
     LOG_PARAM(i) + LOG_PARAM(x.get_size(y)) );
sbi
  • 219,715
  • 46
  • 258
  • 445
1

If you don't want to use stdarg.h which doesn't look good in c++ IMO. you can do something like this. Keep in mind that although this is a small class (you can add to it for better logging), its not the most efficient way to do it.

#include <iostream>
#include <sstream>


class Log
{
public:
    Log() : os()
    {

    }

    ~Log()
    {
        fprintf(stderr, "%s\n", os.str().c_str());
    }

    template<typename T>
    std::ostringstream &operator<<(const T &t)
    {
        os << "Log file - " << t;
        return os;
    }

private:
    std::ostringstream os;
};

int main(int argc, char *argv[])
{
    //usage
    for (int i = 0; i < 10; ++i)
        Log() << "Hello world " << i;

    return 0;
}
nexes
  • 23
  • 1
  • 6
0

Look at stdarg standard library. It allows you to handle variable number of parameters.

Juraj Blaho
  • 13,301
  • 7
  • 50
  • 96
-7

In case you can't or won't use boost:

void Log::writeSuccess(const char* const fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    char buff[1024];
    vsnprintf(buff, sizeof(buff), fmt, ap);
    logfile << buff;
}

Note: it assumes that the written length is limited.

Update: with gcc it's possible to do this in a type-safe way, you need the following declaration.

class Log {
    void writeSuccess(const char* const fmt, ...) __attribute__ ((format (printf, 2, 3)));
    //...
};

Info here. Note: it's a warning, not a compile error. If you ignore warnings that's your problem..:)

Karoly Horvath
  • 94,607
  • 11
  • 117
  • 176
  • 7
    Magic numbers make puppies CRY with how horrifically bad they are. Do not ever write code like this. – Puppy Jul 27 '11 at 11:41
  • 2
    red alert, red alert, magic number in a simple illustrating example *sigh* – Karoly Horvath Jul 27 '11 at 11:44
  • @DeadMG What do you mean by magic number? How would you write it understand. – Publeus Grant Jul 27 '11 at 11:48
  • *instead -sorry about the typo – Publeus Grant Jul 27 '11 at 12:00
  • 2
    The "magic number" refers to the 1024 in `char buff[1024]`. This places a hard-coded maximum length on the string that can be output, but nothing prevents a longer string being passed as a parameter. –  Jul 27 '11 at 12:01
  • 1
    ... in which case it will be truncated. any number you specify could be theoretically a limit that breaks expected functionality, but in reality if you set a good limit these solutions work just fine. off. – Karoly Horvath Jul 27 '11 at 12:05
  • What if I wanted to make it the exact length of the passed in characters? – Publeus Grant Jul 27 '11 at 12:07
  • In other words, what would the code look like avoiding these magic numbers? – Publeus Grant Jul 27 '11 at 12:08
  • @Publeus: `#define MAXIMUM_LOGLINE_LENGTH 1024`... and then silently wait for the c++ evangelists who will try to lynch you for not using `const int`. If you pass it as a parameter the size is going to be dynamic and you have to allocate the space. – Karoly Horvath Jul 27 '11 at 12:16
  • 3
    `vsnprintf` is not type-safe and shouldn't be used in C++, we have much better alternatives. – Cat Plus Plus Jul 27 '11 at 14:37
  • @yi_H The updated code does not work for me. How would I format that into a function? – Publeus Grant Jul 27 '11 at 17:12
  • Error. Expected a ; This is in Visual Studio and attribute is underlined in red too. – Publeus Grant Jul 27 '11 at 17:23
  • @yi_H Thank you anyway. I will use your first answer and keep in mind that I need the buffer to be larger than the largest size I will use. Thanks again! – Publeus Grant Jul 27 '11 at 17:34
  • 7
    @yi_H: You should never illustrate bad practice. Expecting the questioner to fill in a few blanks for themselves is one thing, but illustrating things which are completely and utterly wrong is another. You should compute the space required and allocate it dynamically, then output that. – Puppy Jul 27 '11 at 21:15
  • @DeadMG: I don't know which planet you are living, but on mine this is a perfectly fine solution if you *know* that your output is relatively small. This is probably true for 90% of the use cases, and where this fails, you obviously choose something else. Maybe you are writing extremly robust programs, well, some people write fast ones and they not going to dynamically allocate&free space for each log call. C++ has many applications, and for some scenarious one practice is better than another, so maybe you should think about this next time you state things like "completely and utterly wrong". – Karoly Horvath Jul 27 '11 at 22:13
  • @yi_H: On my planet such "safe enough" solutions have blown up way to often. Yes, that works for 90% of all cases. The other 10% is buffer overflow exploits. If you don't know the length of the strings thrown at you until runtime, all you can do is to allocate at runtime. There is no other solution that is correct. As to fast vs. robust: That's a myth. See [this answer](http://stackoverflow.com/questions/1346583/most-common-reasons-for-unstable-bugs-in-c/1346631#1346631). – sbi Jul 27 '11 at 23:16
  • 2
    -1 You answered his problem, but it's a poison pill.. Hacks like these are fine unless you ever use them in production code, and to be honest, I don't want to have to keep track of what will eventually be critical and what won't, so performance/hacks take a back seat to correctness every time for me and my customers. The question shows a critical lack of understanding, you didn't do him any favors by offering a hackish fix and not addressing the underlying problems. – Josh Jul 27 '11 at 23:28
  • "If you don't know the length of the strings thrown at you until runtime, all you can do is to allocate at runtime." - we completely agree. But as I said, for the other cases it is a correct solution. With gcc type checks I don't see how you could *ever* get an exploit. I would even say that you are know what you are doing even type checks aren't necessary (you tested your code, right?). If I follow your logic then we should abandon pointers as they are dangerous and responsible for a lot of bugs. Well, if you can't use pointers properly, then don't use them. I will. Live and let live. – Karoly Horvath Jul 27 '11 at 23:29
  • @Josh: I use this on production code. Log lines are way smaller than 1024. I have a SIGSEGV handler installed that will stack-trace if something goes wrong. soo.. if it ever crashes on those lines I will let you know. right here. off. – Karoly Horvath Jul 27 '11 at 23:36
  • @yi_H You are defending your practice by installing a signal handler? C'mon, now you are just being argumentative. If you know enough to install a signal handler, you understand why you had to, and the implications of what that brings (for example, how your one function now risks the state of the entire process) I shouldn't be really having to explain myself further. Let me know how that goes when the customer calls and you have to check out out a stacktrace at 4 am. Ridiculous. – Josh Jul 27 '11 at 23:43
  • Well, there are a dozen of non-common things in my program, eg an object pool so I don't have to allocate and initialize memory. All because of performance, so I can saturate the two 10G network cards when serving the clients. Seems to be robust, but *yes*, I have installed a signal handler so if it *ever* goes wrong I can hotfix it. I didn't install the handler because that vsnprintf line that I *know* will never fail. Ridiculous. – Karoly Horvath Jul 27 '11 at 23:58
  • 2
    yi_H: so it's ok to have a buffer overflow that results in security vulnerabilities, as long as it doesn't crash and trigger your SIGSEGV handler? Wow.... it must be nice to be a programmer on your planet. But I'd hate to have to use any of your software. It's perfectly fine IMO to have a fixed-size buffer to avoid dynamic memory allocations if the performance hit would be unacceptable. But allowing input of unknown length to be written into that buffer is inexcusable. – jalf Jul 28 '11 at 07:58
  • For the 100th time, this code does *not* buffer overflow... I can't believe that nobody sees that *n* in that printf function and the length limit it the function call. – Karoly Horvath Jul 28 '11 at 08:13
  • ___1)___ @Publeus wrote: "I will use your first answer and keep in mind that I need the buffer to be larger than the largest size I will use." That's the classical problem with providing hacks as possible solutions: those taught will remember the hacks, and will forget that they were accompanied with a big red "Don't do this!" In teaching programming for more than a decade, I have learned that you must not take shortcuts in the code you present, not even in what you consider irrelevant details, because your students still lack the knowledge to judge relevance of details. – sbi Jul 28 '11 at 09:05
  • ___2)___ I have worked with small and big (MLoC's), with short-lived and long-lived (decades) software. One thing they all have in common is that, sooner or later, they break expectations you had when you wrote your code. Someone puts in a logging statement dumping several lines (intermingled with `'\n'` characters), and either spends two days trying to track down the resulting crash (doing as Publeus does and going with `std::sprintf()`), or spends half a day debugging to see why only 2.5 of his 20 lines are printed. And that's one of the more mild consequences. – sbi Jul 28 '11 at 09:06