Who Architected/Designed C++'s iOStreams, and Would It Still Be Considered Well-Designed by Today's Standards

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?

alt text
Red + x = Failure

alt text
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::couts 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



Leave a reply



Submit