Who architected / designed C++'s IOStreams, and would it still be considered well-designed by today's standards?
Several ill-conceived ideas found their way into the standard: auto_ptr
, vector<bool>
, valarray
and export
, just to name a few. So I wouldn't take the presence of IOStreams necessarily as a sign of quality design.
IOStreams have a checkered history. They are actually a reworking of an earlier streams library, but were authored at a time when many of today's C++ idioms didn't exist, so the designers didn't have the benefit of hindsight. One issue that only became apparent over time was that it is almost impossible to implement IOStreams as efficiently as C's stdio, due to the copious use of virtual functions and forwarding to internal buffer objects at even the finest granularity, and also thanks to some inscrutable strangeness in the way locales are defined and implemented. My memory of this is quite fuzzy, I'll admit; I remember it being the subject of intense debate some years ago, on comp.lang.c++.moderated.
Are the C formatted I/O functions (printf, sprintf, etc) more popular than IOStream, and if so, why?
Personally, I use printf
over the iostream
stuff (like cout
) because I think it's clearer.
When you do formatting with iostream
, you have to <<
all sorts of weirdness like setiosflags
and setf
. I can never remember which namespace all this stuff lives in, let alone what it all does. Even when I do, I'm disappointed with how verbose and unintuitive the code looks.
The formatting options with printf
may seem illegible at first, but they're concise, clearly documented in a single manual page, and common to a wide range of languages.
Another advanage is that printf
is stateless: Unlike with cout
, I don't need to remember which member functions have been called on printf
, or which byzantine concoction of flags has been <<
'ed into it. This is a big plus for readability.
How do I indicate success and failure with colour?
Red + x = Failure
Green + checkmark = Success
How to write custom input stream in C++
The proper way to create a new stream in C++ is to derive from std::streambuf
and to override the underflow()
operation for reading and the overflow()
and sync()
operations for writing. For your purpose you'd create a filtering stream buffer which takes another stream buffer (and possibly a stream from which the stream buffer can be extracted using rdbuf()
) as argument and implements its own operations in terms of this stream buffer.
The basic outline of a stream buffer would be something like this:
class compressbuf
: public std::streambuf {
std::streambuf* sbuf_;
char* buffer_;
// context for the compression
public:
compressbuf(std::streambuf* sbuf)
: sbuf_(sbuf), buffer_(new char[1024]) {
// initialize compression context
}
~compressbuf() { delete[] this->buffer_; }
int underflow() {
if (this->gptr() == this->egptr()) {
// decompress data into buffer_, obtaining its own input from
// this->sbuf_; if necessary resize buffer
// the next statement assumes "size" characters were produced (if
// no more characters are available, size == 0.
this->setg(this->buffer_, this->buffer_, this->buffer_ + size);
}
return this->gptr() == this->egptr()
? std::char_traits<char>::eof()
: std::char_traits<char>::to_int_type(*this->gptr());
}
};
How underflow()
looks exactly depends on the compression library being used. Most libraries I have used keep an internal buffer which needs to be filled and which retains the bytes which are not yet consumed. Typically, it is fairly easy to hook the decompression into underflow()
.
Once the stream buffer is created, you can just initialize an std::istream
object with the stream buffer:
std::ifstream fin("some.file");
compressbuf sbuf(fin.rdbuf());
std::istream in(&sbuf);
If you are going to use the stream buffer frequently, you might want to encapsulate the object construction into a class, e.g., icompressstream
. Doing so is a bit tricky because the base class std::ios
is a virtual base and is the actual location where the stream buffer is stored. To construct the stream buffer before passing a pointer to a std::ios
thus requires jumping through a few hoops: It requires the use of a virtual
base class. Here is how this could look roughly:
struct compressstream_base {
compressbuf sbuf_;
compressstream_base(std::streambuf* sbuf): sbuf_(sbuf) {}
};
class icompressstream
: virtual compressstream_base
, public std::istream {
public:
icompressstream(std::streambuf* sbuf)
: compressstream_base(sbuf)
, std::ios(&this->sbuf_)
, std::istream(&this->sbuf_) {
}
};
(I just typed this code without a simple way to test that it is reasonably correct; please expect typos but the overall approach should work as described)
When virtual inheritance IS a good design?
If you have an interface hierarchy and a corresponding implementation hierarchy, making the interface base classes virtual bases is necessary.
E.g.
struct IBasicInterface
{
virtual ~IBasicInterface() {}
virtual void f() = 0;
};
struct IExtendedInterface : virtual IBasicInterface
{
virtual ~IExtendedInterface() {}
virtual void g() = 0;
};
// One possible implementation strategy
struct CBasicImpl : virtual IBasicInterface
{
virtual ~CBasicImpl() {}
virtual void f();
};
struct CExtendedImpl : virtual IExtendedInterface, CBasicImpl
{
virtual ~CExtendedImpl() {}
virtual void g();
};
Usually this only makes sense if you have a number of interfaces that extend the basic interface and more than one implementation strategy required in different situations. This way you have a clear interface hierarchy and your implementation hierarchies can use inheritance to avoid the duplication of common implementations. If you're using Visual Studio you get a lot of warning C4250, though.
To prevent accidental slicing it is usually best if the CBasicImpl
and CExtendedImpl
classes aren't instantiable but instead have a further level of inheritance providing no extra functionality save a constructor.
Trying to ignore all whitespace up to the first character (desperately needing a simple nudge)
As a preface, I want to state that this is a question made by a student, but unlike most of their type, it is a quality question that merits a quality answer, so I'll try to do it ;). I won't try to just answer your concrete question, but also to show you other slight problems in your code.
First of all, let's analyze your code step by step. More or less like what a debugger would do. Take your time to read this carefully ;)...
#include <iostream>
#include <CTYPE.h>
Includes headers <iostream>
and <ctype.h>
(the uppercase works because of some flaws/design-decisions of NTFS in Windows). I'ld recommend you to change the second line to #include <cctype>
instead.
using namespace std;
This is okay for any beginner/student, but don't get an habit of it! For the purposes of "purity", I would explicitly use std::
along this answer, as if this line didn't existed.
int ReadInt (unsigned short int &UserIn);
Declares a function ReadInt
that takes a reference UserIn
to type unsigned short int
and returns an object of type int
.
int main()
{
Special function main
; no parameters, returns int
. Begin function.
int Error;
unsigned short int UserInput;
char RepeatProgram;
Declares variables Error
, UserInput
, and RepeatProgram
with respective types int
, unsigned short int
, and char
.
do
{
Do-while block. Begin.
Error=ReadInt(UserInput);
Assign return value of ReadInt
of type int
called with argument UserInput
of type int&
to variable Error
of type unsigned short int
.
if (Error==0)
std::cout << "Number is " << UserInput << endl;
If Error
is zero, then print out UserInput
to standard output.
else if (Error==1)
std::cout << "Illegal Data Entry\n";
else if (Error==2)
std::cout << "Numerical overflow, number too big\n";
Otherwise, if an error occurs, report it to the user by means of std::cout
.
std::cout << "Continue? n/N to quit: ";
std::cin >> RepeatProgram;
Query the user if he/she wants to continue or quit. Store the input character in RepeatProgram
of type char
.
std::cout << std::endl;
Redundant, unless you want to add padding, which is probably your purpose. Actually, you're better off doing std::cout << '\n'
, but that doesn't matters too much.
} while (RepeatProgram!='N' && RepeatProgram!='n');
Matching expression for the do-while block above. Repeat execution of the given block if RepeatProgram
is neither lower- or uppercase- letter N
.
}
End function main
. Implicit return value is zero.
int ReadInt (unsigned short int &UserIn)
{
Function ReadInt
takes a reference UserIn
to unsigned short int
and returns an object of type int
. Begin function.
int Err=0;
char TemporaryStorage;
long int FinalNumber=0;
Declares variables Err
, TemporaryStorage
, and FinalNumber
of respective types int
, char
, and long int
. Variables Err
and FinalNumber
are initialized to 0
and 0
, respectively. But, just a single thing. Didn't the assignment said that the output number be stored in a unsigned short int
? So, better of this...
unsigned short int FinalNumber = 0;
Now...
std::cout << "Enter a number: ";
//std::cin.ignore(1000, !' '); this didn't work
Eh? What's this supposed to be? (Error: Aborting debugger because this makes no logic!**). I'm expecting that you just forgot the //
before the comment, right? Now, what do you expect !' '
to evaluate to other than '\0'
? istream::ignore(n, ch)
will discard characters from the input stream until either n
characters have been discarded, ch
is found, or the End-Of-File is reached.
A better approach would be...
do
std::cin.get(TemporaryStorage);
while(std::isspace(TemporyStorage));
Now...
std::cin.get(TemporaryStorage);
This line can be discarded with the above approach ;).
Right. Now, where getting into the part where you obviously banged your head against all solid objects known to mankind. Let me help you a bit there. We have this situation. With the above code, TemporaryStorage
will hold the first character that is not whitespace after the do-while loop. So, we have three things left. First of all, check that at least one digit is in the input, otherwise return an error. Now, while the input is made up of digits, translate characters into integers, and multiply then add to get the actual integer. Finally, and this is the most... ahem... strange part, we need to avoid any overflows.
if (!std::isdigit(TemporaryStorage)) {
Err = 1;
return Err;
}
while (std::isdigit(TemporaryStorage)) {
unsigned short int OverflowChecker = FinalNumber;
FinalNumber *= 10; // Make slot for another digit
FinalNumber += TemporaryStorage - '0'; '0' - '0' = 0, '1' - '0' = 1...
// If an unsigned overflows, it'll "wrap-around" to zero. We exploit that to detect any possible overflow
if (FinalNumber > 65535 || OverflowChecker > FinalNumber) {
Err = 2;
return Err;
}
std::cin.get(TemporaryStorage);
}
// We've got the number, yay!
UserIn = FinalNumber;
The code is self-explanatory. Please comment if you have any doubts with it.
std::cout << TemporaryStorage;//I'm only displaying this while I test my ideas to see if they are working or not, before I move onto the the next step
cout << endl;
return Err;
Should I say something here? Anyway, I already did. Just remember to take that std::cout
s out before showing your work ;).
}
End function ReadInt
.
Why the streams in C++?
Streams have better type safety.
For instance printf("%s", a);
can go horribly wrong if a
is an integer. cout << a;
doesn't have this problem.
Another issue is that streams better conform to Object Oriented design methodologies.
For instance you have a simple application that writes some output and then you want the output to go to a file instead of to the console. With C calls you'll have to replace all of the calls to printf
to calls to fprintf
and take care to maintain the FILE*
along the way. With streams you just change the concrete class of the stream you're using and that's it, most of the code remains the same like so:
void doSomething(ostream& output)
{
output << "this and that" << a;
}
doSomething(cout);
doSomething(ofstream("c:\file.txt"));
Related Topics
C++ Return Value, Reference, Const Reference
Convert String to Integer in C++
Difference Between Static in C and Static in C++
How to Overload Operator==() for a Pointer to the Class
Overriding Return Type in Function Template Specialization
Clang VS Gcc - Optimization Including Operator New
What Are Practical Uses of a Protected Constructor
How to Make an Application Thread Safe
Most Efficient Way to Check If All _M128I Components Are 0 [Using <= Sse4.1 Intrinsics]
C++11: Why Does Std::Condition_Variable Use Std::Unique_Lock
Get List of Static Libraries Used in an Executable
How to Generate and Run Native Code Dynamically
Difference Between Size_T and Std::Size_T
Is Static Init Thread-Safe with Vc2010
Finding the Position of the Maximum Element
C++11: "Narrowing Conversion Inside { }" with Modulus
Getting Gdb to Save a List of Breakpoints
Using an Union (Encapsulated in a Struct) to Bypass Conversions for Neon Data Types