0

I recently started to learn C++, and one of the tasks of my "self-made-up program for making things in language clear to me" is develop a class which will have functionality similar to Java strings. So, it should be something like immutable strings, i.e. when you make changes on such a string, it should not change its internal chars array, but instead return a modified copy of itself. It's pretty straightforward, but I encountered some undestanding problems regarding memory management. Things I already know about MM in C++:

  • objects which were created on the stack are disposed when they go out of scope
  • if an object is created in the heap (i.e. with new keyword), it should be disposed manually with delete (unless you use smart pointers.)

But if a class acts like immutable and, because of that fact, its objects implicitly create themselves sometimes, there are memory leak risks, as I can imagine. Here's an example (String is my educational class):

String *str1 = new String("str1"); // creating new object in the heap
String *str2 = new String("str2"); // another one
str1 = str2; // pointer to str1 object is lost, thereby it's memory leak
- or -
str1 += str2; // we haven't disposed old value of str1, now it's replaced by new object made of str1 + str2

Some implemetation code (not including headers, etc.) is below. All questions asked can be read in comments.

String.h

class String {
public:
    explicit String(const char *);
    String operator+(String);
    String operator+(String *);
    String operator=(String);
    String operator=(String *);
    String operator+=(String);
    String operator+=(String *);

/* . . . */

private:
    String() = default;
    String(String *, String *);
    byte *_contents;
    int _length;

String.cpp

String::String(const char *original) {
    int len = std::strlen(original);
    this->_contents = new byte[len + 1];
    std::strcpy((char *)this->_contents, original);
    this->_length = len;
}

String::String(String *first, String *second) {
    /* not so important implementation details */
    this->_contents = new byte[capacity + 1];
    /* more of not so important implementation details*/
}

String String::operator+(String another) {
    return operator+(&another);
}

String String::operator+(String *another) {
    return String(this, another);
}

// clang warning: "operator =() should always return String&",
// and if I correct that, then it says this operator should return only *this. Why?
String String::operator=(String replacement) {
    return operator=(&replacement);
}

String String::operator=(String *replacement) {
    // I guess, now I should dispose internal array, because one object is replaced by another. Right?
    // Or, maybe, it should be "delete(this)"?
    delete[](this->_contents);
    this->_contents = replacement->_contents;
    this->_length = replacement->_length;
    this->_isWide = replacement->_isWide;
    return *this;
}

String String::operator+=(String addition) {
    return operator+=(&addition);
}

String String::operator+=(String *addition) {
    auto concatenated = new String(this, addition);
    // Same question here as above.
    delete[](this->_contents);
    return operator=(concatenated);
}
Dmitry S.
  • 23
  • 1
  • 8
  • Or, maybe, it's just not the way how things are supposed to be made in C++. Anyway, I'm just studying and I'm curious about different aspects of a new language. – Dmitry S. Feb 15 '20 at 20:15
  • 2
    Don't include overloads that take `String *` as a parameter. Your class deals with _Strings_, not _pointer to Strings_. The assignment of one pointer to another `str1 = str2;` is a common problem with raw strings that is avoided with smart pointers. – 1201ProgramAlarm Feb 15 '20 at 20:17
  • There seem to be multiple questions here, most of which have nothing to do with memory management, but with operator overloading idioms. I suggest you focus on writing classes that don't require manual memory management first and/or use `std::vector`/`std::unique_ptr` instead of raw `new`. `std::byte*` as type of the string array is also wrong, it should be `char*`. You seem to also be violating the [rule-of-0/3/5](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three), again something that would not be an issue if you didn't use `new`. – walnut Feb 15 '20 at 20:17
  • In general, `new` should rarely be used in C++. It kind of makes sense for a custom string implementation, but it is certainly wrong in the code using the string implementation, i.e. `String *str1 = new String("str1");` should not be using any pointers or `new`. See https://stackoverflow.com/questions/6500313/why-should-c-programmers-minimize-use-of-new – walnut Feb 15 '20 at 20:21
  • @walnut, it's not std::byte, I forgot to include the directive. It was an alias for unsigned char. I also cannot understand why these questions do not relate to MM. – Dmitry S. Feb 15 '20 at 20:21
  • @DmitryS. Then please use a name that makes it clear that it is a character type. `byte` has a different meaning in C++. `unsigned char` is *usually* also the wrong type. It should probably be `char`, which is the type that most library functions expect for characters. Most of your operator overloads have non-conventional signatures, that seem to cause you trouble. How these are supposed to be done is independent from memory management and would apply to a class without any dynamic memory allocation the same way, see https://stackoverflow.com/questions/4421706 – walnut Feb 15 '20 at 20:24
  • @walnut, thank you for "the rule of three" link, now it's obvious why I get this clang warning. So, basically, for MM in this case I should use copy constructor and copy assignment operator, as it seems to me now. About types: I used unsigned char to store raw string data, so I can deal with multibyte chars (I've posted only a bit of String code above, it has some other features). – Dmitry S. Feb 15 '20 at 20:29
  • @walnut, also, is it then a common idiom in C++ to create objects-local-variables on stack rather then in the heap if we don't need to pass them around? What about global variables and objects which should live as long as program runs (locale manager or queue daemon, for example)? – Dmitry S. Feb 15 '20 at 20:34
  • @DmitryS. You should by-default declare all your variables with automatic storage duration (i.e. on the stack). If you need something to outlive the automatic lifetime, you use `std::unique_ptr` which also gives the object dynamic storage duration (i.e. puts it on the heap). If you need a variable to live for the duration of the program you give it static storage duration by declaring it with the `static` keyword or making it a global variable. I suggest you read the relevant section in the [C++ core guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-resource). – walnut Feb 15 '20 at 20:36
  • Also read through [some good books on C++](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). – walnut Feb 15 '20 at 20:38

0 Answers0