4.2 Examples
This section explains the basics about using the Streams package and uses examples to introduce concepts and classes.
4.2.1 Creating and Using Streams with Only One Streaming Element
The first example shows how to create and use a single streaming element. It creates an instance of class
RWByteToStreambufOutputStreamImp, which uses an instance of the iostreams class
filebuf to write to a file. The complete example is located in directory
...\examples\stream in the file
binaryTerminalWrite.cpp. Only part of the code is presented below.
filebuf fbuf; // 1
fbuf.open("binaryTerminalWrite.dat",ios::out | ios::binary);
RWByteOutputStream binOutputStream =
RWByteToStreambufOutputStreamImp::make(fbuf); // 2
RWByte array_[10]; // 3
unsigned char i;
for(i=0; i<10; i++);
array_[i]= i+64;
// fill up the array with values
try {
for(i=0; i<10; i++)
binOutputStream << array_[i]; // 4
for(i=0; i<10; i++)
binOutputStream.write(array_[i]); // 5
binOutputStream.write(array_,10); // 6
}
catch(const RWIncompleteStreamOperation& e) { // 7
cout << e.why() << endl;
cout << e.elementsProcessed() << endl;
}
catch(const RWExternalStreamException& e) {
cout << e.why() << endl;
}
binOutputStream << array_[0] << array_[1] << array_[2]
<< rwFlush;
The next example implements the previous example’s corresponding input operation. It creates an instance of class
RWByteFromStreambufInputStreamImp, which uses an instance of the iostreams class
filebuf to read from the file written in the previous example. The complete example is located in directory
...\examples\stream in the file
binaryTerminalRead.cpp. Only part of the code is presented below.
filebuf fbuf; // 1
fbuf.open("binaryTerminalWrite.dat", ios::in | ios::binary);
RWByteInputStream binInputStream =
RWByteFromStreambufInputStreamImp::make(fbuf); // 2
try {
RWSize i;
RWByte theByte; // 3
for(i=0; i<10; i++) {
binInputStream >> theByte; // 4
// do something with the value read
}
for(i=0; i<10; i++) {
theByte= binInputStream.read(); // 5
// do something with the value read
}
RWByte array_[10];
RWSize read_= binInputStream.read(array_,10); // 6
// do something with the array of bytes
RWByte until_= 69;
read_= binInputStream.readUntil(array_,10,until_); // 7
if(binInputStream.isGood()) // 8
cout << "The operation succeeded\n";
cout << "The number of byte(s) stored is: " << read_ << endl;
// do something with the bytes stored
if (binInputStream.isGood()) // 8
cout << "The stream is in a valid state" << endl;
else
cout << "The stream is in error state" << endl;
if(binInputStream.isEof()) // 9
cout << "There is no more data available for reading"
<< endl;
else {
cout << "More data is available for reading" << endl;
while(!binInputStream.isEof()) // 9
theByte= binInputStream.read();
// do something with the byte value
} //end else
} // end try
catch(const RWExternalStreamException& e) { // 10
cout << e.why() << endl;
}
RWByte value1, value2, value3;
binInputStream >> value1 >> value2 >> value3;
Two more examples demonstrating how to create and use single streaming element can be found in directory
examples\stream in the files
narrowCharacterTerminalWrite.cpp and
narrowCharacterTerminalRead.cpp. The first example creates an instance of class
RWCharToStreambufOutputStreamImp, which uses an instance of the -iostreams class
filebuf to write to a file. The second example creates an instance of class
RWCharFromStreambufInputStreamImp, which uses an instance of the -iostreams class
filebuf to read from the file written in the previous example.
4.2.2 Creating and Using Chains of Streaming Elements
A chain of streaming elements is the composition of a single terminal streaming element with one or more filtered stream elements. The terminal streaming element is constructed first and then passed to a filtered stream constructor, which in turn can be passed to another filtered stream constructor. Each streaming element is responsible for a particular task and is independent of the task performed by other streaming elements. The filtered stream elements are not aware of the real type of the streaming element that is used internally. They are linked to it by a handle instance.
4.2.2.1 Output Example
The first example shows how to create and use a chain of streaming elements. It creates a Unicode character output stream that encodes UTF-16 characters using the UCS Transformation Format 8-bit form (UTF-8). The UTF-8 Unicode stream is connected to a buffered binary output stream that sends the transformed bytes to a file:
Figure 13 is a representation of the chain of streaming elements used in this example.
The complete example is located in directory ...\examples\stream in the file UnicodeCharacterFilteredWrite.cpp. Only part of the code is presented below.
RWUChar array[17]= {0x0F00, 0x0F13, 0x0F0A, 0x0F3B, 0x0F8A, 0x0F68,
0x0F35, 0x0F61, 0x0F43, 0x0F39, 0x0F7F, 0x0F1E,
0x0F86, 0x0FA4, 0x0F91, 0x0F88, 0x0F0F}; // 1
// initialize the array of UTF-16 character
filebuf fbuf; // 2
fbuf.open("UnicodeCharacterFilteredWrite.dat", ios::out |
ios::binary);
RWByteOutputStream binOutputStream =
RWByteToStreambufOutputStreamImp::make(fbuf); // 3
RWByteOutputStream bufferedBinOutputStream=
RWBufferedByteOutputStreamImp::make(binOutputStream,1024); // 4
RWUCharOutputStream UTF8OutputStream=
RWUCharToUTF8ByteOutputStreamImp::make(bufferedBinOutputStream);// 5
try {
RWSize i;
for(i=0; i<17; i++)
UTF8OutputStream << array[i]; // 6
for(i=0; i<17; i++)
UTF8OutputStream.write(array[i]); // 7
UTF8OutputStream.write(array,17); // 8
}
catch(const RWIncompleteStreamOperation& e) { // 9
cout << e.why() << endl;
cout << e.elementsProcessed() << endl;
}
catch(const RWExternalStreamException& e) {
cout << e.why() << endl;
}
UTF8OutputStream << array[0] << array[1] << array[2] << rwFlush;
Class
RWExternalStreamException returns an error message and an error code. Class
RWIncompleteStreamOperation inherits from class
RWExternalStreamException and is thrown when an operation partially succeeds. In the above example, writing an array of UTF-16 characters might fail after writing some of the characters. An instance of class
RWIncompleteStreamOperation can be queried for the number of elements successfully written. For more information on exceptions, see
Section 4.3, “Error Handling.”4.2.2.2 Input Example
The next example implements the previous example’s corresponding input operation. It creates a UTF-16 character input stream that decodes UTF-16 characters using the UTF-8 form. The UTF-8 Unicode stream is connected to a buffered binary input stream that gets bytes from the file created in the previous example. The class
RWUCharFromUTF8ByteInputStreamImp converts the bytes into UTF-16 characters. The class
RWBufferedByteInputStreamImp is connected to class
RWByteFromStreambufInputStreamImp to provide the buffered byte input stream.
Figure 14 is a representation of the chain of streaming elements used in this example.
The complete example is located in directory ...\examples\stream in the file UnicodeCharacterFilteredRead.cpp. Only part of the code is presented below.
filebuf fbuf; // 1
fbuf.open("UnicodeCharacterFilteredWrite.dat", ios::in |
ios::binary);
RWByteInputStream binInputStream =
RWByteFromStreambufInputStreamImp::make(fbuf); // 2
RWByteInputStream bufferedBinInputStream=
RWBufferedByteInputStreamImp::make(binInputStream,1024); // 3
RWUCharInputStream UTF8InputStream=
RWUCharFromUTF8ByteInputStreamImp::make(bufferedBinInputStream);// 4
try {
RWSize i;
RWUChar theUChar; // 5
for(i=0; i<17; i++) {
UTF8InputStream >> theUChar; // 6
// do something with the UTF-16 character read
}
for(i=0; i<17; i++) {
theUChar= UTF8InputStream.read(); // 7
// do something with the value read
}
RWUChar array[17];
RWSize read_= UTF8InputStream.read(array,17); // 8
cout << "The number of Unicode character(s) read is: "
<< read_ << endl;
// do something with the array of UTF-16 characters
RWUChar until_= 0x0F7F;
read_= UTF8InputStream.readUntil(array,17,until_); // 9
if(UTF8InputStream.isGood()) //10
cout << "The operation succeeded\n";
cout << "The number of Unicode character(s) stored is: "
<< read_ << endl;
// do something with the array of UTF-16 character(s)
if (UTF8InputStream.isGood()) //10
cout << "The stream is in a valid state" << endl;
else
cout << "The stream is in error state" << endl;
if(UTF8InputStream.isEof()) //11
cout << "There is no more data available for reading"
<< endl;
else {
while(!UTF8InputStream.isEof()) //11
theUChar= UTF8InputStream.read();
// do something with the UTF-16 Character
} //end else
} // end try
catch(const RWExternalStreamException& e) { //12
cout << e.why() << endl;
}
RWUChar value1, value2, value3;
UTF8InputStream >> value1 >> value2 >> value3;
4.2.3 Creating and Using Thread-safe Chains of Streaming Elements
A thread-safe chain of streaming elements is a composition of streaming elements in which one of the streaming elements is a synchronized filtered stream. Synchronized filtered streams ensure that each operation carried out on a stream is executed atomically in a multithreaded environment. Synchronized filtered streams can also be used in conjunction with guarded filtered streams to ensure that a group of operations are executed atomically in a multithreaded environment. For more information on the synchronized and guarded filtered streams, see
Section 3.4.3.2, “Synchronized Streams,” and
Section 3.4.3.3, “Guarded Streams.” 4.2.3.1 Creating a Synchronized Data Output Stream
The first example demonstrates how to create and use a thread-safe chain of streaming elements. It creates a synchronized data output stream (
RWSynchronizedDataOutputStreamImp) that connects to a
RWNativeDataToByteOutputStreamImp stream, which encodes C++ base types as a sequence of bytes using their native binary representation.
The
RWNativeDataToByteOutputStreamImp stream is in turn connected to a buffered binary output stream that sends the transformed bytes to a file. The class
RWBufferedByteOutputStreamImp is connected to class
RWByteToStreambufOutputStreamImp to provide the buffered byte output stream. This example requires the Streams package, as well as the Execution Tracing, Thread-compatible Exception, Synchronization, Smart Pointer, Threading, Functor, and Interthread Communication packages from the Threads Module.
Figure 15 is a representation of the chain of streaming elements used in this example.
The complete source for the examples in
Section 4.2.3.2 and
Section 4.2.3.3 is located in
examples\stream\
dataFilteredWrite.cpp. Only part of the code is presented here.
4.2.3.2 Creating and Managing Threads Using Active Objects
The example uses active objects to carry out asynchronous operations on a unique thread-safe data stream. Active objects execute in their own thread. They start their execution upon construction and wait for their processing to terminate upon destruction, if necessary. This example uses two different kinds of active objects. The first one writes ten elements of one of the C++ base types to a data output stream, ensuring that the ten elements are written in one synchronized operation. The second type of active object writes twenty elements to a data output stream, ensuring that each individual operation on the data output stream is synchronized. The code for both active object classes is presented below.
template <class T>
class synchronizeABunch {
public:
synchronizeABunch(RWDataOutputStream& stream, T data) // 1
:data_(data)
,dataOutputStream_(stream)
{
thread_= rwMakeThreadFunctionM(synchronizeABunch<T>,
*this,void,&synchronizeABunch<T>::func); // 2
thread_.start();
}
~synchronizeABunch() {
thread_.join(); // 3
thread_.raise();
}
void func() {
RWDataOutputStream tmpStream =
RWGuardedDataOutputStreamImp::make(dataOutputStream_); // 4
for(int i=0; i<10; i++) {
tmpStream << data_; // 5
}
}
private:
synchronizeABunch(const synchronizeABunch<T>&);
synchronizeABunch<T>& operator=(const synchronizeABunch<T>&);
RWDataOutputStream dataOutputStream_;
T data_;
RWThreadFunction thread_;
};
The code for the second active object is similar to the code presented above with the exception of the func() function, which enforces synchronization at the operation level.
template <class T>
class synchronizeSingle {
public:
synchronizeSingle(RWDataOutputStream& stream, T data)
:data_(data)
,dataOutputStream_(stream)
{
thread_= rwMakeThreadFunctionM(synchronizeSingle<T>,
*this,void,&synchronizeSingle<T>::func);
thread_.start();
}
~synchronizeSingle() {
thread_.join();
thread_.raise();
}
void func() {
for(int i=0; i<20; i++) {
dataOutputStream_ << data_; // 1
rwYield();
}
}
private:
synchronizeSingle(const synchronizeSingle<T>&);
synchronizeSingle<T>& operator=(const synchronizeSingle<T>&);
RWDataOutputStream dataOutputStream_;
T data_;
RWThreadFunction thread_;
};
4.2.3.3 Creating and Sharing a Synchronized Data Output Stream Among Several Active Objects
The following code constructs the thread-safe chain of streaming elements, and then constructs several active objects that use the same chain of streaming elements as the sink of data.
filebuf fbuf; // 1
fbuf.open("dataFilteredWrite.dat", ios::out | ios::binary);
RWByteOutputStream binOutputStream =
RWByteToStreambufOutputStreamImp::make(fbuf); // 2
RWByteOutputStream bufferedBinOutputStream =
RWBufferedByteOutputStreamImp::make(binOutputStream,1024); // 3
RWDataOutputStream dataOutputStream =
RWNativeDataToByteOutputStreamImp::make(bufferedBinOutputStream);// 4
RWDataOutputStream syncDataOutputStream =
RWSynchronizedDataOutputStreamImp::make(dataOutputStream); // 5
try {
// first group of active objects is created
{ synchronizeSingle<double> s1(syncDataOutputStream,64.0); // 6
synchronizeABunch<double> sb1(syncDataOutputStream,3.14159),
sb2(syncDataOutputStream,1.05);
} // 7
// second group of active objects is created
{ synchronizeSingle<int> s1(syncDataOutputStream,67); // 6
synchronizeABunch<int> sb1(syncDataOutputStream,78),
sb2(syncDataOutputStream,99);
} // 7
}
catch(const RWIncompleteStreamOperation& e) { // 8
cout << e.why() << endl;
cout << e.elementsProcessed() << endl;
}
catch(const RWExternalStreamException& e) {
cout << e.why() << endl;
}
4.2.3.4 Active Objects and Data Input Streams
The example in this section implements the previous example’s corresponding input operation. It creates a synchronized data input stream
RWSynchronizedDataInputStreamImp that connects to an
RWNativeDataFromByteInputStreamImp stream, which restores C++ base types from a sequences of bytes. The
RWNativeDataFromByteInputStreamImp stream is then connected to a buffered binary input stream that reads bytes from the file created in the previous example. The class
RWBufferedByteInputStreamImp is connected to class
RWByteFromStreambufInputStreamImp to provide the buffered byte input stream.
This example requires the Streams package, as well as the Execution Tracing, Thread-compatible Exception, Synchronization, Smart Pointer, Threading, Functor, and Interthread Communication packages from the Threads Module.
Figure 16 is a representation of the chain of streaming elements used in this example.
As in the previous example, this example uses active objects to carry out asynchronous operations on a unique thread-safe data stream. Only one type of active object is used. Its purpose is to read ten elements of one of the C++ base types from a data input stream, ensuring that the ten elements are read in one synchronized operation.
The complete example is located in directory ...\examples\stream in the file dataFilteredRead.cpp. The code for the active object class is presented below:
template <class T>
class readABunch {
public:
readABunch(RWDataInputStream& stream) // 1
:dataInputStream_(stream)
{
thread_= rwMakeThreadFunctionM(readABunch<T>,*this,
void,&readABunch<T>::func); // 2
thread_.start();
}
~readABunch() {
thread_.join(); // 3
thread_.raise();
}
void func() {
RWDataInputStream tmpStream =
RWGuardedDataInputStreamImp::make(dataInputStream_); // 4
for(int i=0; i<10; i++) {
tmpStream >> data_; // 5
cout << data_ << ' ';
rwYield();
}
}
private:
readABunch(const readABunch<T>&);
readABunch<T>& operator=(const readABunch<T>&);
RWDataInputStream dataInputStream_;
T data_;
RWThreadFunction thread_;
};
4.2.3.5 Creating and Sharing a Synchronized Data Input Stream Among Several Active Objects
The following code constructs the thread-safe chain of streaming elements, and then constructs several active objects that use the same chain of streaming elements as the source of data.
filebuf fbuf; // 1
fbuf.open("dataFilteredWrite.dat", ios::in | ios::binary);
RWByteInputStream binInputStream =
RWByteFromStreambufInputStreamImp::make(fbuf); // 2
RWByteInputStream bufferedBinInputStream =
RWBufferedByteInputStreamImp::make(binInputStream,1024); // 3
RWDataInputStream dataInputStream =
RWNativeDataFromByteInputStreamImp::make(bufferedBinInputStream);// 4
RWDataInputStream syncDataInputStream =
RWSynchronizedDataInputStreamImp::make(dataInputStream); // 5
try {
{
readABunch<double> sb1(syncDataInputStream),
sb2(syncDataInputStream), // 6
sb3(syncDataInputStream),
sb4(syncDataInputStream);
} // 7
{
readABunch<int> sb5(syncDataInputStream),
sb6(syncDataInputStream), // 6
sb7(syncDataInputStream),
sb8(syncDataInputStream);
} // 7
}
catch(const RWIncompleteStreamOperation& e) { // 8
cout << e.why() << endl;
cout << e.elementsProcessed() << endl;
}
catch(const RWExternalStreamException& e) {
cout << e.why() << endl;
}
4.2.4 Creating and Using Stream Adapter Classes in the Advanced Tools Module
The stream adapter classes adapt other streaming interfaces to the Advanced Tools Module streaming interface. This means that an application designed to use a streaming library such as iostreams can instantly use the Advanced Tools Module Streams package by using an adapter class as the first streaming element.
The Streams package contains adapter classes for both the iostreams library and the Essential Tools Module virtual streams.
4.2.4.1 The iostreams Adapter Classes
The Streams package has four adapter classes that implement the iostreams streambuf interface:
RWStreambufToByteOutputStream, which implements the iostreams
streambuf interface and forwards to an Advanced Tools Module binary output stream
RWStreambufFromByteInputStream, which implements the iostreams
streambuf interface and forwards to an Advanced Tools Module binary input stream
RWStreambufToCharOutputStream, which implements the iostreams
streambuf interface and forwards to an Advanced Tools Module narrow character output stream
RWStreambufFromCharInputStream, which implements the iostreams
streambuf interface and forwards to an Advanced Tools Module narrow character input stream
The iostreams adapter classes support both the Standard (as described in the C++ standard) and classic iostreams libraries.
4.2.4.2 Virtual Streams Adapter Classes in the Essential Tools Module
The Streams package has two adapter classes that implement the Essential Tools Module virtual input/output stream interface:
RWvostreamToDataOutputStream, which implements the Essential Tools Module
vostream interface and forwards to an Advanced Tools Module data output stream
RWvistreamFromDataInputStream, which implements the Essential Tools Module
vistream interface and forwards to an Advanced Tools Module data input stream
4.2.4.3 Architecture of the Adapter Classes
The main architectural difference between the stream adapter classes and the other stream concrete classes is that the adapter classes are not constructed using a public static make() function, and they are not manipulated using a handle class. The adapter classes provide a public constructor that takes a handle to the stream element to which they forward a request.
4.2.4.4 Using the Adapter Classes for Output
The first example of the Streams package’s adapter classes creates a RWStreambufToCharOutputStream object that connects to an instance of class RWCharToStreambufOutputStreamImp, which in turn uses an iostreams filebuf as a sink of narrow characters. The instance of the adapter class RWStreambufToCharOutputStream is used to construct an iostreams object of type ostream, which is used as the sink of data in the rest of the example.
Figure 17 is a representation of the chain of streaming elements used in this example.
The complete example is located in directory ...\examples\stream in the file adapterWrite.cpp. Only part of the code is presented below.
filebuf fbuf; // 1
fbuf.open("adaptorWrite.dat", ios::out);
RWCharOutputStream charOutputStream =
RWCharToStreambufOutputStreamImp::make(fbuf); // 2
RWStreambufToCharOutputStream streambufAdaptor(charOutputStream);// 3
ostream os(&streambufAdaptor); // 4
try {
double f= 3.14159;
int i= 567;
char* text="Wagabunga";
// insert a bunch of things
os << f << ' ' << i << ' ' << text; // 5
}
catch(const RWIncompleteStreamOperation& e) { // 6
cout << e.why() << endl;
cout << e.elementsProcessed() << endl;
}
catch(const RWExternalStreamException& e) {
cout << e.why() << endl;
}
4.2.4.5 Using the Adapter Classes for Input
The following example implements the previous example’s corresponding input operation. It creates a
RWStreambufFromCharInputStream object that connects to an instance of class
RWCharFromStreambufInputStreamImp, which in turn uses an iostreams
filebuf as a source of narrow characters. The instance of the adapter class
RWStreambufFromCharInputStream is used to construct an iostreams object of type
istream, which is used as the source of data in the rest of the example.
Figure 18 is a representation of the chain of streaming elements used in this example.
The complete example is located in directory ...\examples\stream in the file adapterRead.cpp. Only part of the code is presented below.
filebuf fbuf; // 1
fbuf.open("adaptorWrite.dat", ios::in);
RWCharInputStream charInputStream =
RWCharFromStreambufInputStreamImp::make(fbuf); // 2
RWStreambufFromCharInputStream streambufAdaptor(charInputStream);// 3
istream is(&streambufAdaptor); // 4
try {
double f;
int i;
char text[40];
// extract a bunch of things
is >> f >> i >> text; // 5
cout << '\n' << f << '\n' << i << '\n' << text << '\n' << endl;
}
catch(const RWIncompleteStreamOperation& e) { // 6
cout << e.why() << endl;
cout << e.elementsProcessed() << endl;
}
catch(const RWExternalStreamException& e) {
cout << e.why() << endl;
}