Reimplement cstring_to_double() without using C++ standard library.

Some implementations of C++ streams (notably libstdc++ under OS X) are buggy
and change the global locale to implement support for imbue(), which is not
thread-safe, and so could introduce subtle bugs in multi-thread programs.

Replace the old correct but not working in practice code with a new ugly
version only supporting decimal comma and period which is not totally correct
in theory but should work fine in practice (and shouldn't make things worse
even for the hypothetical locales using some other decimal separator anyhow).
This commit is contained in:
Vadim Zeitlin
2014-03-31 14:39:27 +02:00
parent 4d0785e4df
commit 1260d4f7d9

View File

@@ -10,8 +10,8 @@
#include "soci/error.h"
#include <locale>
#include <sstream>
#include <stdlib.h>
#include <string.h>
namespace soci
{
@@ -26,18 +26,47 @@ namespace details
// point as decimal separator, and nothing but it. If it does, the converted
// number is returned, otherwise an exception is thrown.
inline
double cstring_to_double(std::string const & str)
double cstring_to_double(char const* s)
{
using namespace std;
// Unfortunately there is no clean way to parse a number in C locale
// without this hack: normally, using std::istringstream with classic
// locale should work, but some standard library implementations are buggy
// and handle non-default locale in thread-unsafe way, by changing the
// global C locale which is unacceptable as it introduces subtle bugs in
// multi-thread programs. So we rely on just the standard C functions and
// try to make them work by tweaking the input into the form appropriate
// for the current locale.
double d;
istringstream is(str);
is.imbue(locale::classic());
is >> d;
// First try with the original input.
char* end;
double d = strtod(s, &end);
if (!is || !is.eof())
bool parsedOK;
if (*end == '.')
{
throw soci_error(string("Cannot convert data: string \"") + str + "\" "
// Parsing may have stopped because the current locale uses something
// different from the point as decimal separator, retry with a comma.
//
// In principle, values other than point or comma are possible but they
// don't seem to be used in practice, so for now keep things simple.
size_t const bufSize = strlen(s) + 1;
char* const buf = new char[bufSize];
strcpy(buf, s);
buf[end - s] = ',';
d = strtod(buf, &end);
parsedOK = end != buf && *end == '\0';
delete [] buf;
}
else
{
// Notice that we must detect false positives as well: parsing a string
// using decimal comma should fail when using this function.
parsedOK = end != s && *end == '\0' && !strchr(s, ',');
}
if (!parsedOK)
{
throw soci_error(std::string("Cannot convert data: string \"") + s + "\" "
"is not a number.");
}