Previously, SGPath::pathListSep was a char in static memory, that could
be followed by anything (often '\0', as it seems... but not always).
This is (was) dangerous, because it is then tempting to take its address
and pass it to functions expecting a char * corresponding to a
null-terminated string (C-style).
SGPath::pathListSep is now a static array of two const chars: the path
list separator followed by a '\0'. This implies that
&SGPath::pathListSep can now be reliably interpreted as a C-style string
of length 1 (not counting the null terminator), containing only the path
list separator.
See simgear/misc/argparse.hxx for API and documentation
(simgear/misc/argparse_test.cxx also has examples, although argparse.hxx
features a very simple one at the top).
These classes were also presented in
<https://sourceforge.net/p/flightgear/mailman/message/35785019/>.
The popup/no popup logic in SG_LOG() could be wrong before this commit,
because of missing parentheses around uses of the second macro argument.
For instance, this:
SG_LOG(SG_NAVCACHE, t == 0 ? SG_WARN : SG_ALERT, "Message");
could cause a popup window to be displayed even though neither SG_WARN
nor SG_ALERT should do that in the current state of the logging system.
Thanks to Szymon Acedański for finding this.
Add an alternate constructor to each of the following classes:
ZlibAbstractIStreambuf, ZlibCompressorIStreambuf,
ZlibDecompressorIStreambuf, ZlibCompressorIStream and
ZlibDecompressorIStream. These new constructors are passed the source
std::istream wrapped inside an std::unique_ptr instead of by reference,
and store the unique_ptr object as an instance member. This ensures that
the source std::istream object is available as long as the
ZlibDecompressorIStreambuf, etc. instance is alive (which is necessary
for its getInputData() method) without any additional work for callers,
and that it is automatically destroyed afterwards.
This is particularly useful when writing functions that create and
return an object 'zobj' whose type is a subclass of
ZlibAbstractIStreambuf, when the source std::istream is only of interest
for its role of feeding data to 'zobj'. For instance:
std::unique_ptr<simgear::ZlibCompressorIStream>
myZlibCompressorIStreamProducer(std::string str)
{
std::unique_ptr<std::istringstream> iss(new std::istringstream(str));
return std::unique_ptr<simgear::ZlibCompressorIStream>(
new simgear::ZlibCompressorIStream(std::move(iss))); // new ctor here
}
Callers of such a function get access to a new ZlibCompressorIStream
instance fed by an std::istringstream object ('iss'), but they don't
even have to know this detail, nor to take any measure to ensure that
'iss' lives at least as long as the ZlibCompressorIStream object. The
std::unique_ptr<std::istream> pointing to 'iss' and stored as a member
of the ZlibCompressorIStream object by its constructor automatically
takes care of this lifetime problem.
New automated test for ZlibDecompressorIStreambuf::xsgetn(). xsgetn() is
called by sgetn() from the base class std::streambuf. In our case,
xsgetn() is actually defined in the base class ZlibAbstractIStreambuf
(subclass of std::streambuf), therefore this new test also applies to
ZlibCompressorIStreambuf and the two other related classes,
ZlibCompressorIStream and ZlibDecompressorIStream.
This test asks [x]sgetn() the largest possible amount of chars every
time it is called, i.e., the largest value that can be represented by
std::streamsize. This exercises the code in interesting ways due to the
various types involved (zlib's uInt, std::size_t and std::streamsize,
which have various sizes depending on the platform).
Compilation of these files was disabled in commit
e21ad4b5c1.
Fix build errors and warnings:
- Ambiguous template parameters for std::min();
- No appropriate default constructor available for
std::basic_istream<char,std::char_traits<char>> (the std::istream
subclasses didn't explicitly call the std::istream constructor,
which requires an argument). This is presumably exactly the reason
why sg_gzifstream is declared like this:
class sg_gzifstream : private gzifstream_base, public std::istream
where gzifstream_base is an empty shell for a stream buffer object:
struct gzifstream_base
{
gzifstream_base() {}
gzfilebuf gzbuf;
};
This ensures that the stream buffer object (gzbuf) is initialized
before std::istream's constructor is called. I solved this problem
in a different way, hopefully easier to understand, and requiring
neither an additional class nor multiple inheritance: first, we
initialize the std::istream base with a nullptr as the
std::streambuf * argument (this is valid C++11), then in the
constructor bodies for ZlibCompressorIStream and
ZlibDecompressorIStream, we call std::istream::rdbuf() to attach the
std::istream instance to the now-initialized stream buffer object.
- Possible truncation of constant value on 32 bits systems (this was
in zlibMaxChunkSize() which is now removed, see below).
Type-related improvements:
- Remove zlibMaxChunkSize() and zlibMaxChunk: in C++, one can simply
use std::numeric_limits<uInt>::max()---most of the code in
zlibMaxChunkSize() was there only to find this value via a zlib
function call.
- Add helper function templates zlibChunk() and clipCast().
- Split preparation of the putback area out of
ZlibAbstractIStreambuf::xsgetn() to a new utility method:
xsgetn_preparePutbackArea().
- More rigorous type handling in zlibstream_test.cxx.
Some precautions are necessary because the IOStreams API uses
std::streamsize in many places (e.g., the return value of
std::istream::gcount()), but functions such as the following
std::string constructor:
std::string(const char* s, std::size_t n);
work with std::size_t instead. Since these types are different and
opaque, this requires some care!
Add:
- two stream buffer classes (ZlibCompressorIStreambuf and
ZlibDecompressorIStreambuf), both based on the same abstract class:
ZlibAbstractIStreambuf;
- two std::istream subclasses (ZlibCompressorIStream and
ZlibDecompressorIStream), each creating and using the corresponding
stream buffer class from the previous item.
All these allow one to work with RFC 1950 and RFC 1952 compression
formats, respectively known as the zlib and gzip formats.
These classes are *input* streaming classes, which means they can
efficiently handle arbitrary amounts of data without using any disk
space nor increasing amounts of memory, and allow "client code" to pull
exactly as much data as it wants at any given time, resuming later when
it is ready to handle the next chunk.
See comments in simgear/io/iostreams/zlibstream.hxx for more details.
- Rename zfstream.cxx (resp. zfstream.hxx) to gzfstream.cxx (resp.
gzfstream.hxx)
This is because these files only deal with the gzip format (RFC 1952),
while zlib can actually read and write two slightly different formats:
this one and the "ZLIB Compressed Data Format" (RFC 1950). Since I am
going to add std::streambuf and std::istream subclasses able to deal
with both formats (and supporting data sources that are general
std::istream instances, not just files), this renaming will make
things a bit clearer, I hope.
- Add new folder simgear/io/iostreams and move the following files to
this folder:
simgear/misc/gzcontainerfile.cxx
simgear/misc/gzcontainerfile.hxx
simgear/misc/gzfstream.cxx
simgear/misc/gzfstream.hxx
simgear/misc/sgstream.cxx
simgear/misc/sgstream.hxx
simgear/misc/sgstream_test.cxx
- Adapt other files accordingly (mainly #includes and CMakeLists.txt
files).