I want to convert static data structures using cstrings in our legacy codebase to dynamic ones by using std::strings instead. The problem is that, right now, it is unfeasible to adapt all the functions in our codebase that use them.
I proposed using a wrapper class (property proxy pattern) that exposes a cstring API using a std::string internally. This would allow the usage of cstring functions (strcpy, strlen, memset, etc.) and make the data structures backward-compatible. This is what I have so far:
Original data structure:
struct data
{
char property1[20];
char property2[20];
}
New structure:
struct data
{
CStringProperty property1;
CStringProperty property2;
}
Wrapper class:
class CStringProperty
{
public:
CStringProperty(){}
CStringProperty(std::string& value) : _value{value}{}
CStringProperty(char* value) : _value{value}{}
CStringProperty& operator=(std::string value)
{
_value = value;
return *this;
}
CStringProperty& operator=(char* value)
{
std::string valueStr = value;
return operator=(valueStr);
}
operator std::string()
{
return _value;
}
operator char*()
{
return &_value[0];
}
char& operator[](int i)
{
return _value[i];
}
std::string to_str()
{
return _value;
}
private:
std::string _value;
friend std::ostream& operator<<(std::ostream& os, CStringProperty& value);
};
std::ostream& operator<<(std::ostream& os, CStringProperty& value)
{
return os << value.to_str();
}
Tests
int main()
{
CStringProperty a= "Hello";
std::cout << "Test 1: " << a << std::endl; // Works
CStringProperty b;
b = a;
std::cout << "Test 2: " << b << std::endl; // Works
CStringProperty c;
char d[10] = "Hello";
c = d;
std::cout << "Test 3: " << c << std::endl; // Works
char* e = a;
std::cout << "Test 4: " << e << std::endl; // Works
CStringProperty f;
std::cout << "Test 5: " << f << std::endl; // Works
CStringProperty g = "Hello";
g[0] = 'X';
std::cout << "Test 6: " << g << std::endl; // Works
CStringProperty h = "Hello";
std::cout << "Test 7: " << std::to_string(strlen(h)) << std::endl; // Works
CStringProperty p = "hello";
strcpy(p,"bye");
std::cout << "Test 8.1: " << std::to_string(strlen(p)) << std::endl; // Works
std::cout << "Test 8.2: " << p << std::endl; // Doesn't work
CStringProperty q;
strcpy(q,"bye");
std::cout << "Test 9.1: " << std::to_string(strlen(q)) << std::endl; // Works
std::cout << "Test 9.2: " << q << std::endl; // Doesn't work
return 0;
}
I'm aware of std::string::data() exposing the underlying cstring, but this returns a const char*
, so it's obviously only meant for reading purposes. Using a const_cast
I supose would be similar to what I'm doing above and leads to undefined behaviour.
I would be extremely discouraged to know there's no way of doing this, since it would mean a truly tremendous refactoring effort, so I'm thankful for any help or ideas.
CodePudding user response:
The reason why 9.2 and 8.2 do not work is because std::string
has a size_
data member. strcpy
function does not modify it.
You can use friend
functions (or regular functions) to workaround this issue.
class CStringProperty
{
public:
CStringProperty(){}
CStringProperty(std::string const& value) : _value{value}{}
CStringProperty(char const* value) : _value{value}{}
CStringProperty& operator=(std::string value)
{
_value = value;
return *this;
}
CStringProperty& operator=(char* value)
{
std::string valueStr = value;
return operator=(valueStr);
}
operator std::string()
{
return _value;
}
operator char const*()
{
return _value.c_str();
}
char& operator[](int i)
{
return _value[i];
}
private:
std::string _value;
friend std::ostream& operator<<(std::ostream& os, CStringProperty const& value)
{
return os << value._value;
}
friend void strcpy(CStringProperty& value, char const* src)
{
value._value = src;
}
friend size_t strlen(CStringProperty const& value)
{
return value._value.size();
}
};
The other issue is you do not use const
where needed. And also you pass std::string
by value, which could affect the performance.