2

Consider the following two classes:

class Parent
{
private:
    char outputBuffer[100];

protected:
    virtual void WriteOutput() = 0;
};


class Child : public Parent
{
private:
    double value1;
    double value2;
    void WriteOutput()
    {
        sprintf(outputBuffer, "%.2f %.2f", value1, value2);
    }
};

The parent defines a virtual function that is then implemented in the child, the purpose of which is to write a formatted string to an output buffer.

My challenge is that I would prefer that the output buffer not be directly visible to the child class. In other words, I want WriteOutput to look like:

void WriteOutput()
{
    myFcn("%.2f %.2f", value1, value2);
}

But then what does myFcn() look like? It should just relay to sprintf but the syntax needed to support the varags in sprintf is confusing me. Is it possible to relay a variable number of arguments? I'm showing two values in the example but different children will have different number of values so I have to preserve the vararg capability of sprintf. Note that performance is important here so I can't have unnecessary string copies.

If there's a better / more efficient alternative to sprintf in this context I can consider it. Thanks.

Ami Tavory
  • 74,578
  • 11
  • 141
  • 185
Gadzooks34
  • 1,718
  • 2
  • 20
  • 29
  • 1
    Is [this](http://stackoverflow.com/questions/1056411/how-to-pass-variable-number-of-arguments-to-printf-sprintf?rq=1) what you're looking for? – chris Jan 28 '16 at 16:48
  • @chris That answer is from 2009. C++11 has much better possibilities. – Konrad Rudolph Jan 28 '16 at 16:52
  • Instead of using `vsprintf` at bottom, use a C++ variadic template function. This was one of the most commonly offered examples when variadic templates were introduced. – Cheers and hth. - Alf Jan 28 '16 at 16:54
  • Since the function is `virtual`, a `template` won't be applicable (directly). – 5gon12eder Jan 28 '16 at 17:03
  • @KonradRudolph, Agreed. That was purely for how to pass along the varargs. – chris Jan 28 '16 at 17:04
  • @5gon12eder Fair point. However, rather than mucking around with va_args I’d still use a template and then have a nonvirtual function template wrapper that captures the arguments inside a vector of `std::string`s (or alternatively `std::any` to preserve the original types) and passes them on to the virtual function. Of course that no longer works with `snprintf`. – Konrad Rudolph Jan 28 '16 at 17:40

2 Answers2

3

In Sutter and Alexandrescu's C++ Coding Standards, you can find the following item:

  1. Consider making virtual functions nonpublic, and public functions nonvirtual. 68

I believe that, even though your question did not specifically pertain to the public/private aspects, the rationale there carries here. A non-virtual method should say "externally" what it does, and a virtual method should say how it does it.

As such, they need not have the same signature. In fact, how about this:

class Parent
{
private:
    char outputBuffer[100];

protected:
    virtual void WriteOutputImp(char outputBuffer[100]) = 0;

public:
    void WriteOutput()
    {
        WriteOutputImp(outputBuffer);
   }
};


class Child : public Parent
{
private:
    double value1;
    double value2;

protected:
    virtual void WriteOutputImp(char outputBuffer[100])
    {
        sprintf(outputBuffer, "%.2f %.2f", value1, value2);
    }
};

Edit As @5gon12eder correctly (IMHO) mentions, while you're at it, you might consider changing the char ...[100] to something safer. That is entirely correct, but a separate point.

Ami Tavory
  • 74,578
  • 11
  • 141
  • 185
  • I think this is a very good answer but the `void WriteOutputImp(char outputBuffer[100])` signature is misleading. I'd rather have the function accept a pointer and a length and internally use `snprintf`. – 5gon12eder Jan 28 '16 at 17:04
  • @5gon12eder Thanks, that sounds very logical. Since that is an orthogonal point, though, then in order to avoid confusion, I think I'll mention it as an after-point. Appreciated! – Ami Tavory Jan 28 '16 at 17:06
0

You can use the new variadic templates. i.e.

#include <iostream>
#include <iomanip>

void write()
{  }

template<typename... Params>
void write(double toPrint, Params... args)
{
    std::string str = "%.2f";
    const int size = sizeof...(args);
    if(size > 0U)
        str += " ";
    sprintf(outputBuffer, str.c_str(), toPrint);

    write(args...);
}

You may call this from the individual WriteOutput method like write(5.1, 1.23, mydoublevalue, 9.993). However, you may want to minimize sprintf calls, making another method necessary:

template<typename... Params>
std::string construct()
{ return std::string(); }

template<typename... Params>
std::string construct(double toPrint, Params... args)
{
    std::string str = "%.2f";
    const int size = sizeof...(args);
    if(size > 0U)
        str += " ";
    str += construct(args...);

    return str;
}

template<typename... Params>
void write(Params... args)
{
    sprintf(outputBuffer, (construct(args...)).c_str(), args...);
}

Call is the same, but you use just one sprintf call. See it working online!

NaCl
  • 2,683
  • 2
  • 23
  • 37