Hi,
I've recently starting porting my codebase towards using std::string_view, which is part of the new C++17-standard. I've actually implemented my own version of it, which might be important later when talking about the problem I now face.
So at first it seemed really straight-forward, just replace pretty much every occurance of "const std::string&" with "sys:StringView", except for places where I can take advantage of move-semantics. But then I ran into issues, at places where functions from the standard c-library/WinAPI were being called, like this:
void copy(sys::StringView stInName, sys::StringView stOutName)
{
std::ifstream ifs(stInName.Data(), std::ios::binary);
std::ofstream ofs(stOutName.Data(), std::ios::binary);
if(!ifs.is_open())
throw fileException();
else if(!ofs.is_open())
throw fileException();
ofs << ifs.rdbuf();
}
Oups, std::ifstream expects a null-terminated c-string, and sys::StringView can (and often will) point to a portion of a char-array that is not null-terminated, ie. if a Substr() is generated from another stringview. This obviously means subtle fails when trying to open a file, convert a string to an int, etc... as the function will ignore the size stored in the string-view, and will continue to read characters until the end of the actual string.
Now my question to those who have already used std::string_view, or just had a thorough thought about it: How would you solve this situation? The premise is to use std::string_view as much as possible due to the inherent benefits not only for speed but also for clearity. The ideas I so far had:
- Just pass "const std::string&" to the functions expecting a c-string. Pretty messy, as most other code uses or generates StringViews, which means I have to manually convert to std::string whenever I call such an function.
- Before a call that expects a c-string, convert StringView to std::string and use that. While it technically shouldn't matter as most of the calls requiring a c-string are expensive by themselves, it kind of defeats the purpose of using the StringView in the first place, and actually make some functions slower then if I were just passing a "const std::string&".
- Make a "CStringView" class the always has to point to a null-terminated string. Would be the same as StringView, except it can only be created from an std::string or const char* directy. This would then be used for functions that require a c-string. I've tried it a bit but ran into problems with functions like the one above that are called from many different places with different data, especially when being used in my data-driven content pipeline, since its not trivial to make sure that when I call the function, I actually have a null-terminated string anymore.
- Make an "ToAPI"-function, which looks like this:
template<size_t Size>
std::array<Type, Size> _ToAPI(void) const
{
std::array<Type, Size> vArray;
CopyInto(vArray.data(), Size);
vArray[Size()] = '\0';
return vArray;
}
This actually saves the dynamic allocations made by converting to a string (and hopefully the array would benefit from copy-ellision/RVO), though it cannot be used with every type of string, since you have to specific the max-size beforehands. This is actually the solution I went with right now, as it allows me to hide the requirement for a real c-string on the callees side, should have a neglectible performance overhead and be somewhat safe (I can add checks that the string doesn't surpass the specified size). In addition, I'm rewriting most simpler functions that usually require a c-string (atoi, _strtoi64, ...) so they can work directly with my StringView-class.
Though I'm still not 100% happy, and wondering if there are any other options. So what did/would you do? Any one or combination fo the above; or something entirely different? Seeing how string_view is actually designed without a guarantee for 100% safety, I'm still "shocked" at how difficult things can get when interacting with "outdated" APIs...
↧