Why imbue with boost::posix_time::time_facet with

2019-07-25 01:30发布

问题:

I have a test program, based on CPPUNIT library and Qt that runs ~900 unit tests. This program is deployed on Android using QtCreator. It links with ~80 libraries, each one defining some tests.

After a code change, test program started crashing due to std::bad_cast being thrown.

Original code (no bad_cast):

class TimeUnit
{
    friend std::ostream& operator<<(std::ostream &os, const TimeUnit& var);
public:

    std::string TimeUnit::toString() const
    {
        std::stringstream stream;
        static std::string facetStr = "%Y/%m/%d %H:%M";

        stream.imbue(std::locale(std::locale::classic(), new boost::posix_time::time_facet(facetStr.c_str())));
        stream << *m_time;
        return stream.str();
    }
private:
    boost::posix_time::ptime m_time;
};

std::ostream& operator<<(std::ostream &os, const TimeUnit& var)
{
    os << var.toString();
    return os;
}

New code (makes the test program throw bad_cast at some point):

class TimeUnit
{
    friend std::ostream& operator<<(std::ostream &os, const TimeUnit& var);
public:

    std::string TimeUnit::toString() const
    {
        std::stringstream stream;
        stream << *this;
        return stream.str();
    }
private:
    boost::posix_time::ptime m_time;
};

std::ostream& operator<<(std::ostream &os, const TimeUnit& var)
{
    static std::string facetStr = "%Y/%m/%d %H:%M";

    os.imbue(std::locale(std::locale::classic(), new boost::posix_time::time_facet(facetStr.c_str())));
    // this next line ends up throwing std::bad_cast!
    os << *(var.m_time);
    return os;
}

The new code makes std::bad_cast exception be thrown by my operator<< at some point. According to the doc:

An exception of this type is thrown when a dynamic_cast to a reference type fails the run-time check (e.g. because the types are not related by inheritance), and also from std::use_facet if the requested facet does not exist in the locale.

...boost operator<< for boost::posix_time::ptime does a use_facet...

After a lot of investigation, I can conclude that:

  • The crash is Android-specific (Window works just prefectly)
  • It is due to attaching a facet to std::cout (in the new code, if I create a local std::stringstream apply the facet to it and later copy the result with os << str.str(), no more crash)
  • os.imbue(std::locale(std::locale::classic(),...) or os.imbue(std::locale(os.getloc(),...)) both will lead to the same crash
  • Removing os.imbue call makes the crash disappear
  • Calling os.imbue(std::locale::classic()); before returning from my operator<< does not fix the issue

Unfortunately, I tried to isolate the problem in a MCVE but could not reproduce the problem with some more basic code...

So my question is (are):

  • Am I doing something wrong in my operator<<?
  • Why would this code work on Windows and crash on Android? Could this be due to an insane C++ runtime that would delete the facet in a wrong way?

Edit:

After some more investigation, I found out that the bad_cast is only thrown if I redirect std::cout to a file from my main, using:

class ScopedRedirect
{
public:
    ScopedRedirect(std::ostream & inOriginal, std::ostream & inRedirect) :
        mOriginal(inOriginal),
        mOldBuffer(inOriginal.rdbuf(inRedirect.rdbuf()))
    {}
    virtual ~ScopedRedirect()
    {
        mOriginal.rdbuf(mOldBuffer);
    }

    ScopedRedirect(const ScopedRedirect&) = delete;
    ScopedRedirect& operator=(const ScopedRedirect&) = delete;

protected:
    std::ostream & mOriginal;
    std::streambuf * mOldBuffer;
};

int main( int argc, char* argv[] )
{
    std::fstream output( "cout.txt", std::ios_base::out );
    assert( output.is_open() );

    ScopedRedirect redirectCount( std::cout, output );

    ...
}