Home > Software engineering >  std::string class wrapped with cstring API (property proxy pattern)
std::string class wrapped with cstring API (property proxy pattern)

Time:02-18

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.

  • Related