-3

My project asks to implement a string class in C++, however, I'm confused about one of the public function

class String
{
private:
    char* buffer;
    int size;
    
public:
    // Initializes a string to be empty (i.e., its length will
    // be zero and toChars() will return "").
    String();

    // Initializes a string to contain the characters in the
    // given C-style string, which is assumed to be null-terminated.
    String(const char* chars);

    // Initializes a string to be a copy of an existing string,
    // meaning that it contains the same characters and has the
    // same length.
    String(const String& s);

    // Destroys a string, releasing any memory that is being
    // managed by this object.
    ~String() noexcept;
};

Besides String(const char* chars); function, I implemented all of them correctly, but I don't have any clues about how to implement this one.

Edited: Since c++ standard library cannot be used, I have to compute the size of chars by not using strlen()

String::String(){
    size = 0;
    buffer = nullptr;
}

String::String(const char* chars){
    int i = 0;
    for (char* p = chars;*p != '\0'; p++){
        ++i;
    }
    size = i;

    buffer = new char[size+1];
    i = 0;
    for(;i<size;i++){
        buffer[i] = chars[i];
    }

    buffer[i] = '\0';
}

String::String(const String& s){
    size = s.size;
    buffer = new char[size];
    for int(i=0;i<size;i++){
        buffer[i] = s.buffer[i];
    }
}

String::~String() noexcept{
    delete[] buffer;
}
Alex
  • 175
  • 1
  • 8
  • 2
    `strlen` gets you the length of the string and once you have that the rest could be the same. – Retired Ninja Apr 05 '21 at 11:53
  • 2
    A "C-style" string (e.g. `char*` or `const char*`) *is* a null-terminated array of characters. So yes, the `const char*` constructor should treat the argument as such. And remember that the null-terminator is *not* counted by `strlen` (and typically not by the usual "size" either). – Some programmer dude Apr 05 '21 at 11:53
  • Remember the rule of five! If you handle raw pointers, you need to implement all of the following: the destructor, copy- and move constructor and copy- and move assignment operator. – JHBonarius Apr 05 '21 at 12:37
  • Yes, your `String` class, at the very least, lacks a basically mandatory user-defined assignment operator. Your class can't do something simple like this correctly: `int main() { String s1; // fill s1 with characters... String s2; s2 = s1; }` – PaulMcKenzie Apr 05 '21 at 12:41
  • @Alex It would no have made the code less readable. You note that leaving it out prompted 2 comments in this thread. Second `{ String temp(s); std::swap(temp.size, size); std::swap(temp.buffer, buffer); return *this; }` -- fits perfectly fine in the small comment box. Believe it or not, that's the entire assignment operator. – PaulMcKenzie Apr 05 '21 at 12:48
  • 2
    "standard library cannot be used" Should have noted that before getting 3 answers using the standard library. – Aykhan Hagverdili Apr 05 '21 at 12:58
  • @Alex *Since c++ standard library cannot be used, I have to compute the size of chars by not using strlen()* -- I am going out on a limb here and say that this is probably not what was meant by "not using the standard library". Do you use `cout`? That's in the C++ standard library. What was probably meant by your teacher is usage of `std::string`, `std::vector`, etc. -- already made container classes. It is obvious from the two member variables you have in your `String` class that you have no choice but to use primitive loops and regular C-style functions. – PaulMcKenzie Apr 05 '21 at 13:10
  • 1
    @Ðаn -- I'm in agreement with you. I think the OP went overboard into what was meant by "no usage of standard library". What I believe is that the teacher meant to say not cheat by using `std::string` or `std::vector`. But not using `strlen()` -- that's almost unbelievable. If those really were the restrictions, then you're writing C++ as if walking on eggshells -- every other line of code may be in violation of "not using the standard library". – PaulMcKenzie Apr 05 '21 at 13:17

2 Answers2

1

You should be using std::vector<char> for your buffer so that you don't explicitly call new[] and delete[]. std::vector is part of C++ (and has been for a long time), so this certainly is "implement[ing] a string class in C++ ..." (and does not use std::string).

class String final
{
    std::vector<char> buffer;

public:
    String() = default;
    String(const char* chars){
       auto begin = chars;
       auto end = begin + strlen(chars);
       buffer.insert(buffer.begin(), begin, end);
   }
   String(const String& s){
      buffer = s.buffer;
   }
   ~String() = default;
};

Notice how much simpler the various constructors are now. This has the added advantage of avoiding memory-leaks and being exception safe; and without you having to even (hardly) think about those concerns.

If you really want to avoid using std::vector (why? it's perfect for this case), then at the very least you should use std::unique_ptr<char[]> (or maybe std::shared_ptr<char[]>) which were added to C++11.

class String final
{
    std::unique_ptr<char[]> buffer;
public:
    String() = default;
    ~String() = default;
    String(const String&) = delete;
    String& operator=(const String&) = delete;
    String(const char* chars) {
       const auto len = strlen(chars);
       buffer = std::make_unique<char[]>(len + 1);
       strcpy(buffer.get(), chars);
    }
};

Your most recent edit doesn't make much sense. As can be seen from strlen() even an seemingly "easy" function can be hard to get right; that's why we have standard libraries. And if you can't use any standard library, then there's not even any way to dynamically allocate memory as that's done with malloc().

Ðаn
  • 10,934
  • 11
  • 59
  • 95
0

There are some differences in how much memory you allocate which will come back and bite you.

  • Your default constructor doesn't allocate anything.
  • Your converting constructor allocates size + 1 chars.
  • Your copy constructor allocates size chars.
  • You also need to add a move constructor and copy+move assignment operators.

I suggest that you allocate size + 1 everywhere to leave room for the null terminator. This will also make it really easy to implement the toChars() function mentioned in the comments Example:

char* String::toChars() { return buffer; }

Since you're not allowed to use any of the standard library functions, like std::strlen() or std::memcpy(), and since you are likely going to need functions like that more than once, you could write similar functions yourself instead of putting that code inside the functions of your class in multiple places. Define them once and use them many times.

// a function to get the length of a null terminated C string
unsigned len(const char* chars) {
    unsigned result = 0;
    for(;*chars != '\0'; ++chars, ++result) {}
    return result;
}

// A function to copy a certain amount of chars
void cpy(const char* from, char* to, unsigned len) {
    for(; len; --len, ++from, ++to) {
        *to = *from;
    }
}

With those, your constructors would be pretty simple. I've deliberately not used the member initializer-lists here - but please do check them out.

String::String() {                   // default ctor
    size = 0;
    buffer = new char[size + 1]{};   // {} makes it zero initialized
    // buffer[0] = '\0';             // or you could do this instead
}

String::String(const char* chars) {  // converting ctor
    size = len(chars);               // use the len() function
    buffer = new char[size + 1];
    cpy(chars, buffer, size + 1);    // and use the cpy() function
}

String::String(const String& s) {    // copy ctor
    size = s.size;
    buffer = new char[size + 1];
    cpy(s.buffer, buffer, size + 1); // use the cpy() function again
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108