dimanche 21 août 2016

Implementing a String class with implicit conversion to char* (C++)

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 "CxString.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 :

  1. 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)?
  2. 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?
  3. 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)?
  4. 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!

Aucun commentaire:

Enregistrer un commentaire