Implementacja klasy String, segmentation fault przy wypisywaniu

0

Siema.

Implementuję sobie klasę String - tak dla sportu, bo dawno w c++ niczego nie pisałem - i podczas
wypisywania stringa rzuca mi "segmentation fault". Nie wiem o co lata, wszedłem w debugger
i ma tam miejsce dziwna sytuacja, a mianowicie w nieskończoność jest wywoływana funkcja operatora "<<":

#ifndef STRING_H
#define STRING_H


#include <cstddef> 
#include <cstring>
#include <stdexcept>


class String
{
    private:
        char* str_arr;

        size_t size;

        static const size_t npos = -1;
    
    public:
        String()
        {
            this->str_arr = new char[1];
            this->str_arr[0] = '\0';

            this->size = 0;
        }

        String(const char* str_arr)
        {
            this->size = strlen(str_arr);
            this->str_arr = new char[this->size + 1];

            strcpy(this->str_arr, str_arr);
        }

        String(const String& str)
        {
            this->size = str.size;
            this->str_arr = new char[this->size + 1];

            strcpy(this->str_arr, str.str_arr);
        }

        String(size_t n, char c)
        {
            this->size = n;
            this->str_arr = new char[this->size + 1];

            for(int i = 0; i < this->size; ++i)
                this->str_arr[i] = c;
            
            this->str_arr[this->size] = '\0';
        }

        ~String() { delete[] this->str_arr; }

        String operator+(const String& str)
        {
            String tmp_str = *this;
            tmp_str.append(str);

            return tmp_str;
        }

        String operator+(const char* str_arr)
        {
            String tmp_str = *this;
            tmp_str.append(str_arr);

            return tmp_str;
        }

        String& operator+=(const String& str)
        {
            this->append(str);

            return *this;
        }

        String& operator+=(const char* str_arr)
        {
            this->append(str_arr);

            return *this;
        }

        bool operator==(const String& str)
        {
            if(this->size != str.size)
                return false;
            else
            {
                for(int i = 0; i < this->size; ++i)
                {
                    if(this->str_arr[i] != str.str_arr[i])
                        return false;
                }
            }

            return true;
        }

        bool operator==(const char* str_arr)
        {
            if(this->size != strlen(str_arr))
                return false;
            else
            {
                for(int i = 0; i < this->size; ++i)
                {
                    if(this->str_arr[i] != str_arr[i])
                        return false;
                }
            }

            return true;
        }

        char& operator[](size_t index) { return this->str_arr[index]; }

        const char& operator[](size_t index) const { return this->str_arr[index]; }

        String& append(const String& str)
        {
            char prev_str[this->size + 1];
            size_t prev_str_size = this->size;

            strcpy(prev_str, this->str_arr);

            delete[] this->str_arr;

            this->size = prev_str_size + str.size;

            this->str_arr = new char[this->size];

            strcpy(this->str_arr, prev_str);
            strcpy(this->str_arr + prev_str_size, str.str_arr);

            return *this;
        }

        String& append(const char* str_arr)
        {
            char prev_str[this->size + 1];
            size_t prev_str_size = this->size;

            strcpy(prev_str, this->str_arr);

            delete[] this->str_arr;

            this->size = prev_str_size + strlen(str_arr);

            this->str_arr = new char[this->size];

            strcpy(this->str_arr, prev_str);
            strcpy(this->str_arr + prev_str_size, str_arr);

            return *this;
        }

        const char* c_str() const { return this->str_arr; }

        size_t copy(char* str_arr, size_t len, size_t pos = 0)
        {
            if(pos >= this->size || len == 0 || this->size == 0)
                return 0;
            
            size_t possible_chars_to_copy_count = this->size - pos;

            str_arr = new char[possible_chars_to_copy_count];

            if(len >= possible_chars_to_copy_count)
            {
                memcpy(str_arr, this->str_arr + pos, possible_chars_to_copy_count);

                return possible_chars_to_copy_count;
            } 
            else
            {
                memcpy(str_arr, this->str_arr + pos, len);

                return len;
            }
        }

        size_t find(const String& str, size_t pos = 0)
        {
            if(str.size > this->size || pos >= this->size - str.size)
                return String::npos;

            char cur_substr[str.size];

            for(size_t i = 0; i <= this->size - str.size; ++i)
            {
                memcpy(cur_substr, this->str_arr + i, str.size);

                size_t counter = 0;

                for(size_t j = 0; j < str.size; ++j)
                {
                    if(cur_substr[j] != str[j])
                        break;
                    else
                        ++counter;
                    
                    if(counter == str.size)
                        return i;
                }
            }
            
            return String::npos;
        }

        String substr(size_t pos = 0, size_t len = String::npos)
        {
            if(pos >= this->size)
                throw std::out_of_range("Position exceeds string length!");

            if((len > this->size && len != String::npos) || this->size - pos < len)
                return String();
            
            char tmp_str[this->size - pos + 1];

            if(len == String::npos)
                strcpy(tmp_str, this->str_arr + pos);
            else
            {
                memcpy(tmp_str, this->str_arr + pos, len);
                tmp_str[this->size - pos] = '\0';
            }
                
            return String(tmp_str);
        }

        void resize(size_t n)
        {
            if(n != this->size)
            {
                char prev_str[this->size + 1];
                size_t prev_str_size = this->size;

                strcpy(prev_str, this->str_arr);

                this->size = n;
                
                delete[] this->str_arr;

                this->str_arr = new char[this->size];

                if(prev_str_size < this->size)
                {
                    strcpy(this->str_arr, prev_str);
                }
                else
                {
                    for(int i = 0; i < this->size; ++i)
                        this->str_arr[i] = prev_str[i];
                }
            }
        }

        void resize(size_t n, char c)
        {
            if(n != this->size)
            {
                char prev_str[this->size + 1];
                size_t prev_str_size = this->size;

                strcpy(prev_str, this->str_arr);

                this->size = n;
                
                delete[] this->str_arr;

                this->str_arr = new char[this->size];

                if(prev_str_size < this->size)
                {
                    strcpy(this->str_arr, prev_str);

                    for(int i = prev_str_size; i < this->size; ++i)
                        this->str_arr[i] = c;
                }
                else
                {
                    for(int i = 0; i < this->size; ++i)
                        this->str_arr[i] = prev_str[i];         
                }
            }
        }

        void clear()
        {
            delete[] this->str_arr;

            this->size = 0;
        }

        size_t length() const { return this->size; }

        bool empty() const { return this->size == 0; }

        friend std::ostream& operator<<(std::ostream& stream, const String& str);
};

std::ostream& operator<<(std::ostream& stream, const String& str)
{
    stream << str.str_arr;

    return stream;
}

#endif

#include "string.hpp"
#include <iostream>


int main()
{
    String str = "abcd";
    String str2 = "gggg";

    //std::cout << str + "efgh" << std::endl;
    std::cout << str << std::endl;
    std::cout << str << std::endl;
    std::cout << str2 << std::endl;

    String str3 = "defg";

    std::cout << str3.find(str) << std::endl;

    //std::cout << str.substr(2, 3) << std::endl;
    //std::cout << str.substr(1, 3) << std::endl;
}

Pomógłby ktoś, bo już pomysłów nie mam? :D

1

Ja dostałem segmentation fault jak zerowałem memset'em obiekt klasy string. C++ to nie C i trzeba dobrze znać różnice. — shJeff

Mogłeś przejechać przez vpointery

Eldorad O. napisał(a):

Pomógłby ktoś, bo już pomysłów nie mam? :D

Ja mam (Zaznaczam nie chce mi się czytać w sobotę)
Po pierwsze, przemyśl, czy chcesz string immutable (w stylu Javy/C#), to nie tylko w środku wiele upraszcza, ale daje jasność w kodzie klienckim. Mutable POZRNIE daje większy power, ale zaraz zaczynają się schody.

Po drugie, odnośnie konstruktorów kopiujących / operatorów zwracających ugruntuj sobie rule of three/five/zero

https://en.cppreference.com/w/cpp/language/rule_of_three

Po trzecie, musisz zapoznać się z debuggerem. Nikt tego za ciebie nie zrobi. Ustawiasz breakpoint, wpada, analizujesz

1

Tak na 100% nie jestem pewny, ale tutaj:

        String(const char* str_arr)
        {
            this->size = strlen(str_arr);
            this->str_arr = new char[this->size + 1];

            strcpy(this->str_arr, str_arr);
        }

powinieneś oznaczyć ten konstruktor jako explicit.

std::ostream& operator<<(std::ostream& stream, const String& str)
{
    stream << str.str_arr;

char * może być tutaj niejawnie przekonwertowane do String, i masz wtedy rekursywne wołanie.

0

@mwl4: Bardzo możliwe że masz rację, tylko zastanawiają mnie dwie rzeczy:

  1. Dlaczego kompilator próbuje wywoływać niejawnie konstruktor "String", kiedy rzucam
    "char*" do strumienia?

  2. Jeżeli oznaczę ten konstruktor jako "explicit" nie będę mógł stosować zapisu - String str = "abcd" - a przecież
    korzystając z std::string taki zapis jest możliwy.

1

A dodaj tam w drugim pliku #include <ostream> lub iostream.

W dodatku kompilujesz to z wyłączonymi ostrzeżeniami kompilatora bo tam całą konsolę na 5 stron warningami zasypało.

1

Pytanie jeszcze czym ty to kompilujesz. Puściłem podobny kod na godbolcie z gcc 11.3 c++14 i nic takiego co opisujesz się nie dzieje.

https://godbolt.org/z/EWYPoxfxh

Możliwe też, że brakuje ci gdzieś include'a i przez to konwersja jest wymuszona.

0

@mwl4:

@Szalony Programista2:

Faktycznie, to przez brak "#include <iostream>".
Dlaczego w takim wypadku konwersja była wymuszona, a nie rzucony został jakiś błąd czy coś?

3

Dlatego, że jeśli nie ma operator<< dla char * - bo np nie zaincludowałeś ostream, to kompilator szuka możliwych konwersji. A najlepszą konwersją niejawną wtedy jest char* do String i wywołanie operator<< dla const String &.

Więcej tutaj: https://en.cppreference.com/w/cpp/language/implicit_conversion

1 użytkowników online, w tym zalogowanych: 0, gości: 1