0

In C++ string literals "Hello" are const and are immutable. I wanted to make a custom string class whose strings are not const chars, so they can be changeable

Here is a snippet of code that might illustrate what I'm trying to do:

#include <iostream>

class String {
  public: 
    char * p_start;

    String(char * strSourc) // Constructor
      {
        p_start = strSourc;
      }
};

int main() 
{
  String myString("Hello");  
// Create object myString, send "Hello" string literal as argument

  std::cout << myString.p_start << std::endl; 
// Prints "Hello"

  *myString.p_start = 'Y'; 
// Attempt to change value at first byte of myString.p_start

  std::cout << myString.p_start << std::endl; 
// Prints "Hello" (no change)

  myString.p_start = "Yellow"; 
// Assigning a string literal to p_start pointer 

  std::cout << myString.p_start << std::endl; 
// Prints Yellow, change works.  I thought myString "Hello" was const chars, immutable

  return 0;
}

So, I'm confused. I've looked everywhere and it says that string literals, like "Hello", are immutable, each of their char bytes are unchangeable. Though I managed to assign Yellow to the p_start pointer, changing the first letter. Though changing the single letter H to a Y through dereferencing the H pointer didn't do anything.

Any insights would help me, thanks.

Zebrafish
  • 11,682
  • 3
  • 43
  • 119
  • 1
    Could you show your current ctor? And what error did you encounter? – songyuanyao Oct 03 '15 at 07:47
  • I've changed the question to hopefully better illustrate the issue, including sample code. – Zebrafish Oct 03 '15 at 11:02
  • 1
    Yes, you cannot change the content of "Hello" by your pointer `p_start`. You can make a copy of it in the ctor, then you can modify the chars, which being managed by the class. – songyuanyao Oct 03 '15 at 11:27
  • Thanks for that. The thing I don't get is why it let me say p_start = "Yellow" if the string literal is null terminated array of const char, but it wouldn't let me say *p_start = 'Y'. Also, like you said, I'll have to pass it the string literal, then do a one to one copy into my own array, but this brings up the question, I heard string literals are static and last forever during the program until the program stops. Wouldn't this be a huge waste of memory? Basically having two copies of everything? As far as I know you can't destroy string literals, that's what I read. – Zebrafish Oct 03 '15 at 12:58
  • I am unsure how this differs from `string s = "Hello";` if you are trying to make `"Hello"` mutable, you cannot — there must always be a copy. – Dúthomhas Feb 08 '22 at 04:36

2 Answers2

1

I think you're confusing about pointer and pointee.

p_start = "Yellow", you're changing the value of pointer, to point to "Yellow". *p_start = 'Y', you're changing the value of pointee, the content p_start points to, not itself. As you said, "Yellow" are const chars, so the behaviour try to modify them is UB.

You can make a copy of it in the ctor, then you can modify the chars, which are managed by the class. Yes, it will be a new copy, but it won't be a waste of memory. And you have no choice if you want them to be changable.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • I upvoted your answer, thanks. You're right, I did confuse the pointer and the pointee. If you make a pointer to an int, assign it to an address of an int, and then cout << the pointer, you'll get the memory address pointed to by the pointer. If on the other hand, as I did here, you create an array of chars, and then make a pointer pointing to the array of chars, and cout << the pointer, you won't get the address that the pointer points to, you'll get the entire null-terminated string. Why is that? Different rules? Also, how can I assign "Yellow" to a 4 byte pointer? – Zebrafish Oct 04 '15 at 00:32
  • I just found out that when I did p_start = "Yellow", it didn't assign the value of "Yellow" to p_start, rather it created a new block of memory with the string literal "Yellow" and gave the address of it to p_start. Anyway, I have to say I find that string literals being immutable and static a pain in the arse. I'm sure they have their reasons, but if I want to manipulate a string I have to either copy it from the string literal and have two copies of it (cause the original is static and is never destroyed), or create an array and send it to my class in two steps. Anyone think this is silly? – Zebrafish Oct 04 '15 at 02:59
  • 1
    @TitoneMaurice "Different rules?" Yes, `operator<<()` overload for `char *` to print out the content of string, so the behaviour is different from `int *` or `float *` and so on. "How can I assign "Yellow" to a 4 byte pointer?" You don't assign the string to the pointer, just the first address of the string, i.e. the address of "Y" in "Yellow". – songyuanyao Oct 04 '15 at 07:56
  • 1
    @TitoneMaurice Because the string literal can't be modified, the complier is allowed to do some optimization on it, such as save it in ROM, and only one copy in memory for all string literals with same value, and so on. – songyuanyao Oct 04 '15 at 08:46
  • Yeah I understand what you're saying. So string literals are const chars, except when you do this: char myCharArray[ ] = "Hello" I'm happy for regular C++ string literals to be const. But my point is I want my string class that's capable of taking a declaration, like string myString("hello") and thats capable of manipulating the chars (non-const). I know I can create a char array first, and then pass a pointer, that's two steps. Likewise, creating a const string and then copying it, and that's going to last FOREVER as a double copy, is not satisfactory for me if I'm dealing lots of strings – Zebrafish Oct 04 '15 at 09:17
  • I'll give you an example for why I'm complaining. These comments allow you 600 characters. Each comment would be 601 bytes as a string. I pass these strings to my string class and do a one for one copy of each char into allocated memory. If I wanted to have a hundred strings, it would take up 12,200 bytes (6100 * 2) 6100 of which would be useless and last forever on the stack, and have to make 6100 one to one copies. Otherwise I could create 100 char arrays, and then pass a pointer to my string class and copy the pointers to my string class 100 times. And that's just with 100. Get me? – Zebrafish Oct 04 '15 at 09:31
  • @TitoneMaurice If you have lots of string literals, they'll cost many memories, that's true. But I don't think it's a common case. We're creating strings more from user input, from network, and so on. – songyuanyao Oct 04 '15 at 09:34
  • 1
    @TitoneMaurice Comments can't be string literals. They can't be decided at compile time. `const char[] comment1 = "???";` – songyuanyao Oct 04 '15 at 09:37
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/91288/discussion-between-titone-maurice-and-songyuanyao). – Zebrafish Oct 04 '15 at 10:04
0

not sure if anyone finds this helpful after so long but since I am learning myself I took your above code and made it work by copying. It now has a member variable for size and cleans up using delete when you assign a new string literal to it (const char*).

#include <iostream>

class String {
public: 
char * p_start;
int m_size;

String(const char * strSourc) // Constructor
  {
    //The following will get the size of the parameter.
     int SizeParameter=0;
     while (*(strSourc+SizeParameter) != '\0')
     {
         SizeParameter++;
     }
     // size of string saved.
     m_size = SizeParameter; 
     // allocate enough memory so we can copy strSourc
     p_start = new char[SizeParameter+1];
     //copy the contents strSourc
    for(int i=0; i<SizeParameter+1; i++)
    {
    *(p_start+i) = *(strSourc+i);
    }
  }
  //Handle change of string value.
  char* AssignNewString (const char* newtext)
  {
      //clean
      delete p_start; 
      
      int SizeParameter=0;
     while (*(newtext+SizeParameter) != '\0')
     {
         SizeParameter++;
     }
     // size of string saved.
     m_size = SizeParameter; 
     // allocate enough memory so we can copy strSourc
     p_start = new char[SizeParameter+1];
     //copy the contents strSourc
    for(int i=0; i<SizeParameter+1; i++)
    {
    *(p_start+i) = *(newtext+i);
    }
    return p_start;
  }
  char* operator=(const char* newtext)
  {
  AssignNewString(newtext);
  }
  };



 int main() 
 {
 String myString("Hello");  
 // Create object myString, send "Hello" string literal as argument
 std::cout << "string size: " << myString.m_size << std::endl;

 std::cout << myString.p_start << std::endl; 
// Prints "Hello"

*myString.p_start = 'Y'; 
// Attempt to change value at first byte of myString.p_start

std::cout << myString.p_start << std::endl; 
// Prints "Hello" (no change)

myString = "yellow"; 
// Assigning a string literal to p_start pointer 
myString = "THIS IS A LONGER STRING";

std::cout << myString.p_start << std::endl; 

std::cout << "string size: " << myString.m_size << std::endl;
return 0;
}

Note I am learning myself so do let me know if I am doing something wrong. But so far, it seems to work.

MP3D
  • 41
  • 5