Question

I need to be able to use a single fstream to have platform-independent way of using files. In particular, I need to be able to support file paths with unicode characters on Windows with as minimal intrusion into code to support it as possible. As such, it seemed like boost iostreams could provide the answer. However, upon trying to use it, I get two failures which cause me concern. See the following sample code:

// MinGW (MSYS)
// GCC 4.7.2
// Boost 1.50.0
// g++ -g ifstreamtest.cpp -o test.exe -I /t/tools/boost/boost_1_50_0 -L /t/tools/boost/boost_1_50_0/stage/lib -lboost_system-mgw47-mt-d-1_50 -lboost_filesystem-mgw47-mt-d-1_50 -lboost_locale-mgw47-mt-d-1_50 -lboost_iostreams-mgw47-mt-d-1_50

#include <boost/locale.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/filesystem/path.hpp>

namespace MyNamespace
{

typedef ::boost::iostreams::file_descriptor fd;
typedef ::boost::iostreams::stream< ::boost::iostreams::file_descriptor> fstream;
typedef ::boost::iostreams::stream< ::boost::iostreams::file_descriptor_sink> ofstream;
typedef ::boost::iostreams::stream< ::boost::iostreams::file_descriptor_source> ifstream;
} // namespace MyNamespace

int main(int argc, char **argv)
{
    // Imbue boost filesystem codepoint conversions with local system
    // Do this to ensure proper UTF conversion.
    boost::filesystem::path::imbue(boost::locale::generator().generate(""));

    // Test file path.
    boost::filesystem::path file_path("test.txt");


    // Anonymous scope for temporary object.
    {
        // Open file in ctor, write to output, neglect to clean up until dtor.
        MyNamespace::ofstream output(file_path, std::ios_base::out | std::ios_base::app);
        if ( output.is_open() == false ) std::cout << "Unable to open @" << __LINE__ << std::endl;
        output << "test line 1" << std::endl;
        std::cout << "done @" << __LINE__ << std::endl;
    }
    // Temporary object destroyed while still open.

    // Anonymous scope for temporary object.
    {
        // Open file in ctor, write to output, specifically close file.
        MyNamespace::ofstream output1(file_path, std::ios_base::out | std::ios_base::app);
        if ( output1.is_open() == false ) std::cout << "Unable to open @" << __LINE__ << std::endl;
        output1 << "test line 2" << std::endl;
        output1.close();
        std::cout << "done @" << __LINE__ << std::endl;
    }
    // Temporary object destroyed.

    // Anonymous scope for temporary object.
    {
        // Default-ctor; open later. Write to file, neglect to clean up until dtor.
        MyNamespace::ofstream output2;
        // Next line causes "Assertion failed: initialized_, file t:/tools/boost/boost_1_50_0/boost/iostreams/detail/optional.hpp, line 55"
        output2->open(file_path, std::ios_base::out | std::ios_base::app);
        if ( output2.is_open() == false ) std::cout << "Unable to open @" << __LINE__ << std::endl;
        output2 << "blah test test blah" << std::endl;
    }
    // Temporary object destroyed.

//    MyNamespace::ifstream input;
// Compile success, but linker failure:
// s:\reactor\utf8stream/ifstreamtest.cpp:42: undefined reference to `void boost::iostreams::file_descriptor_source::open<boost::filesystem::path>(boost::filesystem::path const&, std::_Ios_Openmode)'
//    input->open(file_path, std::ios_base::in);

    std::cout << "done." << std::endl;
    return 0;
}

On Windows, I am limited to GCC 4.7.2 and Boost 1.50.

The comments explain the two failures, but I will expand on them here. The first and most problematic to me is when I try to use the stream object as a "normal" fstream object. If I open the fstream in its constructor, then all is fine and dandy (as can be seen in the first two anonymous scopes). But if I allocate the fstream object and then attempt to open it later, "Bad Things" happen.

If I try to invoke boost::iostreams::stream::open(), I get compiler errors saying that it can't convert parameter 1 (boost::filesystem::path) to a boost::iostreams::file_descriptor_sink. Why should that not work when it can be constructed with a boost::filesystem::path? In either case, attempting to call boost::iostreams::file_descriptor_sink::open() by using the stream's operator->() fails an assertion (as shown in the third anonymous scope). This is quite evil as it would seem to me that it should throw an exception instead of failing an assertion. Failing an assertion would indicate to me that there's a bug in the boost code.

The second failure I have is that the typedefed fstream and ofstream seem to work (well, compile and link) just fine. But when I try to use the ifstream, I get a linker failure when trying to call ifstream->open(); I get this on Windows (MinGW configuration as stated before) as well as on OS X 10.8.5 with Apple LLVM version 4.2 (clang-425.0.28) (based on LLVM 3.2svn). Since it compiles fine and the only difference between the two there is whether it's a source or a sink... and both should be able to open a file... it makes me think that this is also a bug in boost.

Any thoughts or suggestions?

Was it helpful?

Solution

Regarding the compiler error:

I can reproduce the linking error on Linux, with g++ and Clang++, so it's not a specific Windows problem (I'm also using Boost 1.55.0, so it's not specific to 1.50).

I suppose it is caused by a template definition that is allowed in the header file, but not implemented in the source/library.

Solution (for the linking problem only): Instead of

input->open(file_path, std::ios_base::in);

use

input->open(file_path.string(), std::ios_base::in);

This circumvents the potentially misdefined template by using a string-based constructor.

Regarding the assertion error:

The issue is you need to initialize the file_descriptor_sink separately, else the iostream initialization won't be handled properly. Use this code:

//We need to initialize the sink separately
boost::iostreams::file_descriptor_sink output2Sink(file_path, std::ios_base::out | std::ios_base::app);
MyNamespace::ofstream output2(output2Sink);
if ( output2.is_open() == false ) std::cout << "Unable to open @" << __LINE__ << std::endl;
output2 << "blah test test blah" << std::endl;

open() doesn't seem to reset the optional that causes the assertion.

The same method needs to be applied to the the MyNamespace::ifstream:

boost::iostreams::file_descriptor_source inputSource(file_path, std::ios_base::in);
MyNamespace::ifstream input(inputSource);
//Test reading back what we wrote earlier
std::string inputContent;
input >> inputContent;
//Prints: blah (only the first word is read!)
std::cout << "Read from test.txt: " << inputContent << std::endl;

Also note that it is not neccessary to apply the solution to avoid the compiler error from above.

With these modifications your program appears to be working on my system.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top