patterncppModerate
Formatter class
Viewed 0 times
formatterclassstackoverflow
Problem
In our production code, we cannot use Boost or C++0x. Formatting strings using
In particular, is this line fully-defined:
My belief is that it is OK, but I wanted peer-review.
Three main points of concern:
The
Here is the class definition:
And here is a complete test harness, including the above definition. You should be able to compile & run as-is. Do you see any UB?
sprintf or stringstream is annoying in this case, and this prompted me to write my own little Formatter class. I am curious if the implementation of this class or the use of it introduces any Undefined Behavior.In particular, is this line fully-defined:
Reject( Formatter() << "Error Recieved" << 42 << " " << some_code << " '" << some_msg << "'");My belief is that it is OK, but I wanted peer-review.
Three main points of concern:
- Is there a double-assignment within a single sequence point? Is it UB?
- Do I have a problem with the lifetime of temporaries?
- Does my
Formatterclass (or the intended use of it) introduce any UB?
The
Formatter class has both a (templatized) operator<< and an operator std::string. The intent is to use the Formatter() class as a temporary in place of a std::string parameter for any function taking a const std::string&.Here is the class definition:
class Formatter
{
public:
Formatter() {};
template Formatter& operator<<(Field f)
{
ss_ << f;
return *this;
}
operator std::string() const { return ss_.str(); }
private:
std::stringstream ss_;
};And here is a complete test harness, including the above definition. You should be able to compile & run as-is. Do you see any UB?
#include
#include
#include
#include
class Formatter
{
public:
Formatter() {};
template Formatter& operator<<(Field f)
{
ss_ << f;
return *this;
}
operator std::string() const { return ss_.str(); }
private:
std::stringstream ss_;
};
void Reject(const std::string& msg)
{
std::cout << "Recieved Message: '" << msg << "'" << std::endl;
}
int main()
{
const char& some_code = 'A';
const char* some_msg = "Something";
Reject( Formatter() << "Error Recieved" << 42 << " " << some_code << " '" << some_msg << "'");
}Solution
In addition to what's already been said, I would:
-
Mark the stringstream as public. This won't affect most uses of your code, and can already be hacked around with a custom manipulator to get at the "internal" stream object, but it will enable those that need to access the internal stream (such as to avoid the string copy inherent in the stringstream interface), and know the specifics of their implementation that allow what they want, to do so. Of course, 0x move semantics allay much of this need, but are still Not Quite Here Yet™.
-
Check the stream before returning the string; if it's in a failed state, throw an exception (or at least log the condition somewhere before returning a string). This is unlikely to occur for most uses, but if it does happen, you'll be glad you found out the stream is failed rather than screw with formatting while wondering why "it just won't work".
Regarding double-assignment, there's no assignment at all. The sequence points should be mostly what people expect, but, exactly, it looks like:
The operators order it as if it was function calls, so that:
To add to the discussion on temporaries, all of the temporaries created are in the expression:
They will not be destroyed until the end of the full expression containing that some_function call, which means not only will the string be passed to some_function, but some_function will have already returned by that time. (Or an exception will be thrown and they will be destroyed while unwinding, etc.)
In order to handle all manipulators, such as std::endl, add:
I've used this pattern several times to wrap streams, and it's very handy in that you can use it inline (as you do) or create a Formatter variable for more complex manipulation (think a loop inserting into the stream based on a condition, etc.). Though the latter case is only important when the wrapper does more than you have it do here. :)
-
Mark the stringstream as public. This won't affect most uses of your code, and can already be hacked around with a custom manipulator to get at the "internal" stream object, but it will enable those that need to access the internal stream (such as to avoid the string copy inherent in the stringstream interface), and know the specifics of their implementation that allow what they want, to do so. Of course, 0x move semantics allay much of this need, but are still Not Quite Here Yet™.
-
Check the stream before returning the string; if it's in a failed state, throw an exception (or at least log the condition somewhere before returning a string). This is unlikely to occur for most uses, but if it does happen, you'll be glad you found out the stream is failed rather than screw with formatting while wondering why "it just won't work".
Regarding double-assignment, there's no assignment at all. The sequence points should be mostly what people expect, but, exactly, it looks like:
some_function(((Formatter() << expr_a) << expr_b) << expr_c);
// 1 2 3The operators order it as if it was function calls, so that:
- Formatter() and expr_a both occur before the insertion marked 1.
- The above, plus insertion 1, plus expr_b happen before insertion 2.
- The above, plus insertion 2, plus expr_c happen before insertion 3.
- Note this only limits in one direction: expr_c can happen after expr_a and before Formatter(), for example.
- Naturally, all of the above plus the string conversion occur before calling some_function.
To add to the discussion on temporaries, all of the temporaries created are in the expression:
some_function(Formatter() << make_a_temp() << "etc.")
// one temp another temp and so onThey will not be destroyed until the end of the full expression containing that some_function call, which means not only will the string be passed to some_function, but some_function will have already returned by that time. (Or an exception will be thrown and they will be destroyed while unwinding, etc.)
In order to handle all manipulators, such as std::endl, add:
struct Formatter {
Formatter& operator<<(std::ios_base& (*manip)(std::ios_base&)) {
ss_ << manip;
return *this;
}
Formatter& operator<<(std::ios& (*manip)(std::ios&)) {
ss_ << manip;
return *this;
}
Formatter& operator<<(std::ostream& (*manip)(std::ostream&)) {
ss_ << manip;
return *this;
}
};I've used this pattern several times to wrap streams, and it's very handy in that you can use it inline (as you do) or create a Formatter variable for more complex manipulation (think a loop inserting into the stream based on a condition, etc.). Though the latter case is only important when the wrapper does more than you have it do here. :)
Code Snippets
some_function(((Formatter() << expr_a) << expr_b) << expr_c);
// 1 2 3some_function(Formatter() << make_a_temp() << "etc.")
// one temp another temp and so onstruct Formatter {
Formatter& operator<<(std::ios_base& (*manip)(std::ios_base&)) {
ss_ << manip;
return *this;
}
Formatter& operator<<(std::ios& (*manip)(std::ios&)) {
ss_ << manip;
return *this;
}
Formatter& operator<<(std::ostream& (*manip)(std::ostream&)) {
ss_ << manip;
return *this;
}
};Context
StackExchange Code Review Q#226, answer score: 10
Revisions (0)
No revisions yet.