0

I get a blank output. I'm a newbie and have been struggling on this for some time.
I have gotten 0 errors by the compiler.

Also what can be improved on this?

How can I get the length of const char* as an int instead of size_t without having to use static_cast.

#include <iostream>
#include <cassert>

class String
{
private:
    char* Str_Buffer{};
    int Str_Size{};
public:
    String(const char* string = " ")
        : Str_Size{ static_cast<int>(strlen(string)) }
    {
        Str_Buffer = new char[Str_Size];
    }

    String& operator=(const String& string)
    {
        if (this == &string)
            return *this;

        delete[] Str_Buffer;

        Str_Size = string.Str_Size;
        if (string.Str_Buffer)
        {
            Str_Buffer = new char[Str_Size];

            for (int index{ 0 }; index < Str_Size; ++index)
                Str_Buffer[index] = string.Str_Buffer[index];
        }
        return *this;
    }

    char& operator[](const int index)
    {
        assert(index >= 0);
        assert(index < Str_Size);
        return Str_Buffer[index];
    }

    friend std::ostream& operator<<(std::ostream& out, const String& string)
    {
        out << string.Str_Buffer;
        return out;
    }

    ~String()
    {
        delete[] Str_Buffer;
    }
};

int main()
{
    String word("Hello world!");
    std::cout << word;

    return 0;
}
Swordfish
  • 12,971
  • 3
  • 21
  • 43
GigaHiga
  • 39
  • 1
  • 7

2 Answers2

1

I get a blank output.

You don't fill your String::Str_Buffer with meaningful data in the constructor. You could use std::strcpy() from <cstring> to do that. std::strlen() is also declared in that header file. To use std::strcpy() the memory pointed to by String::Str_Buffer needs to be one char bigger than the string you want to copy there because strings in C and C++ are zero-terminated ('\0').

How can I get the length of const char* as an int instead of size_t without having to use static_cast.

Why would you want an int? Sizes of objects in C++ are measured with values of type std::size_t (defined in several headers but when in doubt include <cstddef>). std::size_t is guaranteed to be big enough to handle all object sizes. It is for example the return type of std::strlen() and the sizeof-operator.

Your assignment operator is not exception-safe:

String& operator=(const String& string)
{
    // ...

    delete[] Str_Buffer;  // the old state is now gone

    Str_Size = string.Str_Size;
    if (string.Str_Buffer)
    {
        Str_Buffer = new char[Str_Size];  // when new[] throws, the object
                                          // will be in an undefined state
    // ...

Possible but not elegant solution:

String& operator=(const String& string)
{
    char *temp = new[string.Str_Size];

    // copy string.Str_Buffer to temp

    delete[] Str_Buffer;
    Str_Buffer = temp;
    Str_Size string.Str_Size

    return *this;
}

See Copy-and-Swap for an better solution.


Resource Management

Please familiarize yourself with The Rule of Five and the Copy-and-Swap Idiom.

A starting point for a class that manages a string could look like that:

#include <cassert>   // assert()
#include <cstddef>   // std::size_t
#include <cstring>   // std::strlen(), std::strcpy()
#include <utility>   // std::swap(), std::exchange()
#include <iostream>

class string_t
{
    size_t  length  = 0;
    char   *data    = nullptr;

public:
    string_t() = default;

    string_t(char const *str)
    : length { str ? std::strlen(str) : 0  },
      data   { new char[length + 1]{}      }
    {
        str && std::strcpy(data, str);
    }

    string_t(string_t const &other)  // copy constructor
    : length { other.length            },
      data   { new char[length + 1]{}  }
    {
        other.data && std::strcpy(data, other.data);
    }

    string_t(string_t &&other)  // move constructor
    : length { std::exchange(other.length, 0)      },  // steal others resources and
      data   { std::exchange(other.data, nullptr)  }   // give other a state it's
    {}                                                 // destructor can work with

    string_t& operator=(string_t other)   // assignment operator
    {                                     // mind: other gets copied
        std::swap(length, other.length);  // steal other's resources
        std::swap(data, other.data);      // other's destructor will
    }                                     // take care of ours.

    ~string_t() { delete[] data; }

    std::size_t get_length() const { return length; }

    char& operator[](std::size_t index)
    {
        assert(index < length);
        return data[index];
    }

    // stream-insertion operator:
    friend std::ostream& operator<<(std::ostream &os, string_t const &str)
    {
        return os << (str.data ? str.data : "");
    }
};

int main()
{
    string_t foo{ "Hello!" };  // char const* constructor
    std::cout << foo << '\n';

    string_t bar{ foo };  // copy constructor
    std::cout << bar << '\n';

    string_t qux{ string_t{ "World!" } };  // move constructor (from a temporary)
    std::cout << qux << '\n';

    bar = qux;  // assignment operator
    std::cout << bar << '\n';
}
Community
  • 1
  • 1
Swordfish
  • 12,971
  • 3
  • 21
  • 43
-1

First of all, you need to include for strlen. You get a blank output because the constructor does not write the input string to Str_Buffer. You may use std::copy to copy the memory to the allocated buffer.

You have to use static cast, because strlen returns std::size_t. Just change the type of Str_Size to std::size_t to get rid of the static cast.

Also take a look at the rule of five. Defining a move and copy constuctor will improve performace of your code.

See a working version of your code below:

#include <iostream>
#include <cassert>
#include <cstring>
#include <algorithm>


class String
{
private:
    char* Str_Buffer;
    std::size_t Str_Size;
public:
    String(const char* string = " ")
        : Str_Size{ strlen(string) }
    {
        Str_Buffer = new char[Str_Size];
        std::copy(string, string + Str_Size, Str_Buffer);
    }

    String(const String& other)
       : Str_Size(other.Str_Size)
    {
        Str_Buffer = new char[Str_Size];
        std::copy(other.Str_Buffer, other.Str_Buffer + Str_Size, Str_Buffer);
    }

    String(String && other)
    {
        *this = std::move(other);
    }

    String& operator=(const String& string)
    {
        if (this == &string)
            return *this;

        delete[] Str_Buffer;

        Str_Size = string.Str_Size;
        if (string.Str_Buffer)
        {
            Str_Buffer = new char[Str_Size];

            for (std::size_t index = 0; index < Str_Size; ++index)
                Str_Buffer[index] = string.Str_Buffer[index];
        }
        return *this;
    }

    char& operator[](const int index)
    {
        assert(index >= 0);
        assert(index < Str_Size);
        return Str_Buffer[index];
    }

    friend std::ostream& operator<<(std::ostream& out, const String& string)
    {
        out << string.Str_Buffer;
        return out;
    }

    ~String()
    {
        delete[] Str_Buffer;
    }
};

int main()
{
    String word("Hello world!");
    std::cout << word;

    return 0;
}
Draft25
  • 52
  • 4
  • 2
    `std::copy` is much, much better. It'll be just as fast as `memcpy` (in fact it'll be equivalent to `memmove` for a type like this) except it is type-safe, so doesn't break horribly and catastrophically and potentially _silently_ when you change your types. – Lightness Races in Orbit May 11 '19 at 19:51
  • 2
    Define "working". [Rule of Five](https://en.cppreference.com/w/cpp/language/rule_of_three#Rule_of_five) – Swordfish May 11 '19 at 19:51
  • Well, with working I meant "It produces the desired output". Of course you guys are completely right. I just adjusted my answer. – Draft25 May 11 '19 at 20:28
  • Defining a move and copy constuctor will improve performace of your code. – Nope. A custom copy constructor is *needed* so objects of that type can be safely copied (deep copy). The default copy-ctor does only copy the pointer value, which would lead (amongst other things) to `delete`[]ing the same pointer value twice when more than one destructor ever runs. Your move-ctor should swap the members of `*this` with the members of `other`. Why the for-loop in the assignment operator? Could be exception-save with copy-and-swap. And it is `std::strlen()`. – Swordfish May 11 '19 at 21:54
  • *`out << string.Str_Buffer;`* won't work well because your `Str_Buffer` is not zero-terminated. – Swordfish May 11 '19 at 21:55
  • and why don't you use the initialization list for Str_Buffer but assignment in the constructors bodies? – Swordfish May 11 '19 at 23:35