It might not be advisable according to what I have read at a couple of places (and that's probably the reason std::string doesn't do it already), but in a controlled environment and with careful usage, I think it might be ok to write a string class which can be implicitly converted to a proper writable char buffer when needed by third party library methods (which take only char* as an argument), and still behave like a modern string having methods like Find(), Split(), SubString() etc. While I can try to implement the usual other string manipulation methods later, I first wanted to ask about the efficient and safe way to do this main task. Currently, we have to allocate a char array of roughly the maximum size of the char* output that is expected from the third party method, pass it there, then convert the return char* to a std::string to be able to use the convenient methods it allows, then again pass its (const char*) result to another method using string.c_str(). This is both lengthy and makes the code look a little messy.
Here is my very initial implementation so far:
MyString.h
#pragma once #include<string> using namespace std; class MyString { private: bool mBufferInitialized; size_t mAllocSize; string mString; char *mBuffer; public: MyString(size_t size); MyString(const char* cstr); MyString(); ~MyString(); operator char*() { return GetBuffer(); } operator const char*() { return GetAsConstChar(); } const char* GetAsConstChar() { InvalidateBuffer(); return mString.c_str(); } private: char* GetBuffer(); void InvalidateBuffer(); }; MyString.cpp
#include "MyString.h" MyString::MyString(size_t size) :mAllocSize(size) ,mBufferInitialized(false) ,mBuffer(nullptr) { mString.reserve(size); } MyString::MyString(const char * cstr) :MyString() { mString.assign(cstr); } MyString::MyString() :MyString((size_t)1024) { } MyString::~MyString() { if (mBufferInitialized) delete[] mBuffer; } char * MyString::GetBuffer() { if (!mBufferInitialized) { mBuffer = new char[mAllocSize]{ '\0' }; mBufferInitialized = true; } if (mString.length() > 0) memcpy(mBuffer, mString.c_str(), mString.length()); return mBuffer; } void MyString::InvalidateBuffer() { if (mBufferInitialized && mBuffer && strlen(mBuffer) > 0) { mString.assign(mBuffer); mBuffer[0] = '\0'; } } Sample usage (main.cpp)
#include "MyString.h" #include <iostream> void testSetChars(char * name) { if (!name) return; //This length is not known to us, but the maximum //return length is known for each function. char str[] = "random random name"; strcpy_s(name, strlen(str) + 1, str); } int main(int, char*) { MyString cs("test initializer"); cout << cs.GetAsConstChar() << '\n'; testSetChars(cs); cout << cs.GetAsConstChar() << '\n'; getchar(); return 0; } Now, I plan to call the InvalidateBuffer() in almost all the methods before doing anything else. Now some of my questions are :
- Is there a better way to do it in terms of memory/performance and/or safety, especially in C++ 11 (apart from the usual move constructor/assignment operators which I plan to add to it soon)?
- I had initially implemented the 'buffer' using a std::vector of chars, which was easier to implement and more C++ like, but was concerned about performance. So the GetBuffer() method would just return the beginning pointer of the resized vector of . Do you think there are any major pros/cons of using a vector instead of char* here?
- I plan to add wide char support to it later. Do you think a union of two structs : {char,string} and {wchar_t, wstring} would be the way to go for that purpose (it will be only one of these two at a time)?
- Is it too much overkill rather than just doing the usual way of passing char array pointer, converting to a std::string and doing our work with it. The third party function calls expecting char* arguments are used heavily in the code and I plan to completely replace both char* and std::string with this new string if it works.
Thank you for your patience and help!
var.c_str()when using with a third party libraries? It seems to me a lot of work that could be solved easily and safely just by calling thec_str()from std::stringstd::stringalready allows you do pass achar*: stackoverflow.com/questions/38702943/…char *p = someString;does when looking at the code. Is that\0terminated? May I write to it? How much memory does it point to? It also allows nonsense code such asif(someString)andsomeString + 5;and so on, which tends to hide bugs.