Проблема с деструктором в С++

409
17 февраля 2017, 03:05

Задача - создать класс string. Я прописал конструкторы, перегрузил операции, и программа вроде работает нормально, но в последней строке возникает ошибка выполнения "CRT detected that the application wrote to memory after end of heap buffer". Заголовочный файл класса прилагаю, при необходимости могу прикрепить реализацию.

#pragma once
#include <iostream>
using namespace std;
class my_string
{
private:
    char *string_chars;
    size_t len;
public:
    my_string(const char *string_chars);
    my_string(const size_t len);
    my_string(const my_string &obj);
    my_string();
    ~my_string() { delete[] this->string_chars; }
    my_string operator+(const my_string &b) const;
    my_string operator+(const char *b) const;
    my_string &operator=(const my_string &b);
    my_string &operator=(const char *b);
    my_string &operator+=(const my_string &b);
    my_string &operator+=(const char *b);
    bool operator<(const my_string &b) const;
    bool operator<(const char *b) const;
    bool operator>(const my_string &b) const;
    bool operator>(const char *b) const;
    bool operator==(const my_string &b) const;
    bool operator==(const char *b) const;
    bool operator!=(const my_string &b) const;
    bool operator!=(const char *b) const;
    void print() const { cout << this->string_chars << endl; }
    const char *get_str() const { return this->string_chars; }
    size_t get_len() const { return this->len; }
    size_t first_sym(const char a) const;
};

UPD: Прилагаю реализацию

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string.h>
#include "String.h"
using namespace std;
my_string::my_string(const char *string_chars)
{
    this->len = strlen(string_chars);
    this->string_chars = new char[this->len + 1];
    strcpy(this->string_chars, string_chars);
    this->string_chars[this->len] = 0;
}
my_string::my_string(const size_t len)
{
    this->len = len;
    this->string_chars = new char[this->len + 1];
    for (size_t i = 0; i <= len; i++)
    {
        this->string_chars[i] = 0;
    }
}
my_string::my_string(const my_string &obj)
{
    strcpy(this->string_chars, obj.get_str());
    this->len = obj.get_len();
}
my_string::my_string()
{
    this->len = 0;
    this->string_chars = new char[2];
    for (int i = 0; i < 2; i++)
    {
        this->string_chars[i] = 0;
    }
}
my_string my_string::operator+(const my_string &b) const
{
    size_t len = this->len + b.get_len();
    char *result = new char[len + 1];
    strcpy(result, this->string_chars);
    strcat(result, b.string_chars);
    result[len] = 0;
    my_string *res = new my_string(result);
    delete[] result;
    return *res;
}
my_string my_string::operator+(const char *b) const
{
    size_t len = this->len + strlen(b);
    char *result = new char[len + 1];
    strcpy(result, this->string_chars);
    strcat(result, b);
    result[len] = 0;
    my_string *res = new my_string(result);
    delete[] result;
    return *res;
}
my_string &my_string::operator=(const my_string &b)
{
    if (&b != this)
    {
        this->len = b.get_len();
        strcpy(this->string_chars, b.string_chars);
    }
    return *this;
}
my_string &my_string::operator=(const char *b)
{
    if (b != this->string_chars)
    {
        this->len = strlen(b);
        strcpy(this->string_chars, b);
    }
    return *this;
}
my_string &my_string::operator+=(const my_string &b)
{
    this->len += b.get_len();
    strcat(this->string_chars, b.string_chars);
    return *this;
}
my_string &my_string::operator+=(const char *b)
{
    this->len += strlen(b);
    strcat(this->string_chars, b);
    return *this;
}
bool my_string::operator<(const my_string &b) const
{
    if (strcmp(b.string_chars, this->string_chars))
    {
        return true;
    }
    else
    {
        return false;
    }
}
bool my_string::operator<(const char *b) const
{
    if (strcmp(b, this->string_chars))
    {
        return true;
    }
    else
    {
        return false;
    }
}
bool my_string::operator>(const my_string &b) const
{
    if (strcmp(this->string_chars, b.string_chars))
    {
        return true;
    }
    else
    {
        return false;
    }
}
bool my_string::operator>(const char *b) const
{
    if (strcmp(this->string_chars, b))
    {
        return true;
    }
    else
    {
        return false;
    }
}
bool my_string::operator==(const my_string &b) const
{
    if (!strcmp(this->string_chars, b.string_chars) && this->len == b.len)
    {
        return true;
    }
    else
    {
        return false;
    }
}
bool my_string::operator==(const char *b) const
{
    if (!strcmp(this->string_chars, b) && this->len == strlen(b))
    {
        return true;
    }
    else
    {
        return false;
    }
}
bool my_string::operator!=(const my_string &b) const
{
    if (abs(strcmp(this->string_chars, b.string_chars)) || this->len != b.len)
    {
        return true;
    }
    else
    {
        return false;
    }
}
bool my_string::operator!=(const char *b) const
{
    if (abs(strcmp(this->string_chars, b)) || this->len != strlen(b))
    {
        return true;
    }
    else
    {
        return false;
    }
}
size_t my_string::first_sym(const char a) const
{
    char *sym = strchr(this->string_chars, a);
    if (sym != nullptr)
    {
        return sym - this->string_chars;
    }
    return -1;
}
Answer 1

Подобные классы где динамически выделяется память, должны определять явно конструктор копирования. В вашем классе он не объявлен, а потому его отсутствие может быть причиной возникновения ошибки, так как созданный компилятором неявно конструктор копирования просто выполняет поочередно копирование значений членов класса, что может в результате привести к тому, что два объекта класса будут иметь указатели, указывающие на одну и ту же область памяти, которую деструкторы этих объектов попытается в итоге удалить дважды.

Этот конструктор с параметром

my_string(const int len);
                ^^^^

было бы лучше объявить как

my_string( size_t len );
           ^^^^^^

Данные операторы

my_string &operator+(const my_string &b) const;
my_string &operator+(const char *b) const;

объявлены не корректно. Так как сами функции-члены класса являются константными, значит объект класса, которой расположен в левой части от знака + не изменяется. Отсюда следует, что из операторов возвращается ссылка на временные объекты, созданные внутри операторов, что ведет к неопределенному поведению программы.

Эту функцию

char *get_str() const { return this->string_chars; }

так как она константная лучше объявить с возвращаемым значением const char * , или перегрузить эту функцию для константных и не константных объектов.

Определение этой функции

int first_sym(const char a) const { return strchr(this->string_chars, a) - this->string_chars; }

некорректно, так как в случае отсутствия символа a в строке функция strchr вернет указатель NULL. К тому же тип возвращаемого значения должен быть не int, а, по крайней мере, ptrdiff_t. Конечно лучше было бы объявить тип возвращаемого значения size_t, и если символ не найден в строке, то возвращать значение -1.

Что касается возникающей ошибки, то она, скорей всего, возникает из-за неправильных реализаций операторов, которые вы в своем вопросе не привели, а также неизвестно предложение, в результате выполнения которого возникла эта ошибка. Например, возможно, что вы забыли выделить память для завершающего нуля в операторах суммирования, или "проскакиваете" этот ноль при копировании строк.

EDIT: Так как вы изменили свой ответ, исправив некоторые недостатки, которые я указал (что является плохой идеей, так как это может привести к несоответствию ответа и вопроса), и дополнили его реализацией, то

В данном конструкторе последнее предложение лишнее, и может быть удалено.

my_string::my_string(const char *string_chars)
{
    this->len = strlen(string_chars);
    this->string_chars = new char[this->len + 1];
    strcpy(this->string_chars, string_chars);
    this->string_chars[this->len] = 0;
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

Этот конструктор может быть написан проще

my_string::my_string(const size_t len)
{
    this->len = len;
    this->string_chars = new char[this->len + 1]();
}

Лучше этот конструктор объявить со спецификатором функции explicit

explicit my_string::my_string( size_t len)
{
    this->len = len;
    this->string_chars = new char[this->len + 1]();
}

А иначе получится так, что вы строки можете складывать с целыми числами в виду наличия этого конструктора преобразования.

Или было бы еще лучше объявить его со вторым параметром, который задает символ заполнитель. Например,

#include <cstring>
//...
my_string::my_string( size_t len, char c )
{
    this->len = len;
    this->string_chars = new char[this->len + 1]();
    std::memset(this->string_chars, ( unsigned char )c, len ); 
}

Данный конструктор копирования неверный

my_string::my_string(const my_string &obj)
{
    strcpy(this->string_chars, obj.get_str());
    this->len = obj.get_len();
}

Память, куда копируется строка, не была выделена.

В этом конструкторе по умолчанию достаточно выделить память для одного символа

my_string::my_string()
{
    this->len = 0;
    this->string_chars = new char[1]();
}

Данный оператор некорректный и можт привести к утечке памяти. Нет никакой необходимости создавать объект в динамической памяти

my_string my_string::operator+(const my_string &b) const
{
    size_t len = this->len + b.get_len();
    char *result = new char[len + 1];
    strcpy(result, this->string_chars);
    strcat(result, b.string_chars);
    result[len] = 0;
    my_string *res = new my_string(result);
    delete[] result;
    return *res;
}

Он может быть определен следующим образом

my_string my_string::operator+(const my_string &b) const
{
    size_t len = this->len + b.get_len();
    char *result = new char[len + 1];
    strcpy(result, this->string_chars);
    strcat(result, b.string_chars);
    my_string res(result);
    delete[] result;
    return res;
}

То же самое справедливо и для второго перегруженного оператора operator +.

Простые и составные операторы присваивания определены некорректно. В них в общем случае имеет место выход за пределы выделенной памяти. Необходимо удалять ранее созданную строку, если она не равна по длине присваиваемой, и создавать новую с соответствующей длиной.

Все операторы сравнения могут быть написаны проще. К тому они также написаны неверно. Например, вместо этого неверно написанного оператора

bool my_string::operator<(const my_string &b) const
{
    strcmp(b.string_chars, this->string_chars))
    {
        return true;
    }
    else
    {
        return false;
    }
}

Должно быть

bool my_string::operator<(const my_string &b) const
{
    return strcmp( this->string_chars, b.string_chars ) < 0;
}
READ ALSO
Можно ли использовать один сокет в нескольких потоках?

Можно ли использовать один сокет в нескольких потоках?

Клиентское приложение на C++ передаёт на HTTP-сервер данные - идентификатор клиента (чтобы обозначить присутствие), кадры с веб-камеры (по запросу),...

408
GNU C++ 4.8.4,перевод из int в char mass[]

GNU C++ 4.8.4,перевод из int в char mass[]

Имеется массив mass[] и число int qНеобходимо записать q по разрядам в массив

330
Зачем boost::asio::io_service помещать в отдельный поток?

Зачем boost::asio::io_service помещать в отдельный поток?

Задался вопросом, а зачем в данном примере io_service в разных потоках

333