Как освободить переменную в C ++ Исключение класса деструктор - PullRequest
0 голосов
/ 05 февраля 2019

Я определяю новый класс C ++, метод которого what возвращает тип char* со значением целого числа, переданного в качестве конструктора.

Первоначально я сделал это с использованием класса string и возвратил строкуданные из what.

Затем я пытаюсь использовать тип char* в следующем коде:

/* Define the exception here */
class BadLengthException: public exception
{
  public:
    BadLengthException(int strLength)
    {
        strLen = strLength;
        res = (char*)malloc(strLength+1);
        int resultSize = sprintf(res, "%d", strLen);
    }
    ~BadLengthException() throw()
    {
        free(res);
    }
    virtual const char* what() const throw()
    {
      return res;
    }
  private:
    int strLen;
    char* res;
};

, но у меня возникают проблемы при освобождении выделенной переменной malloc:это дает следующее исключение:

pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6

Так почему же это так?Где и как я должен освободить переменную, выделенную двумерным шрифтом, в классе Exception?

EDIT

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

Просто введите: 1, а затем Me, например

#include <iostream>
#include <string>
#include <sstream>
#include <exception>
using namespace std;

/* Define the exception here */
class BadLengthException: public exception
{
  public:
    BadLengthException(int strLength)
    {
        strLen = strLength;
        res = (char*)malloc(strLength+1);
        int resultSize = sprintf(res, "%d", strLen);
    }
    ~BadLengthException() throw()
    {
        free(res);
    }
    virtual const char* what() const throw()
    {
      return res;
    }
  private:
    int strLen;
    char* res;
};



bool checkUsername(string username) {
    bool isValid = true;
    int n = username.length();
    if(n < 5) {
        throw BadLengthException(n);
    }
    for(int i = 0; i < n-1; i++) {
        if(username[i] == 'w' && username[i+1] == 'w') {
            isValid = false;
        }
    }
    return isValid;
}

int main() {
    int T; cin >> T;
    while(T--) {
        string username;
        cin >> username;
        try {
            bool isValid = checkUsername(username);
            if(isValid) {
                cout << "Valid" << '\n';
            } else {
                cout << "Invalid" << '\n';
            }
        } catch (BadLengthException e) {
            cout << "Too short: " << e.what() << '\n';
        }
    }
    return 0;
}

РЕДАКТИРОВАТЬ 2

Исходный класс, использующий строку, следующий: этот работает

class BadLengthException: public exception
{
  public:
    BadLengthException(int strLength)
    {
        res = to_string(strLength);
    }
    virtual const char* what() const throw()
    {
      return res.c_str();
    }
  private:
    string res;
};

Ответы [ 3 ]

0 голосов
/ 13 февраля 2019

Я добавлю еще один ответ, пытаясь собрать все воедино.

Проблема

Проблема заключается в следующем.Рассмотрим пример:

double division(int a, int b) {
   if( b == 0 ) {
      throw "Division by zero condition!";
   }
   return (a/b);
}

В другом блоке будет вызвана функция:

double c;
try
{
    c =division(d,f)
}
catch( ExceptionName e ) {
    ...
}

Обычно в вызываемых функциях генерируются исключения (т. Е. Генерируется экземпляр) (division inпример выше) но они перехватываются другими частями кода (прямой вызов функции или даже в более внешних функциях).Чтобы сделать экземпляр исключения доступным из того места, где оно было сгенерировано, в место, где оно было перехвачено, экземпляр копируется.

Копирование экземпляров исключений, по-видимому, выполняется с помощью конструктора назначения копирования (=, чтобы очистить).Так как я не объявлял один, используется по умолчанию.Этот конструктор по умолчанию имеет следующее поведение с указателями: вместо копирования указанного значения он копирует само значение указателя (то есть адрес), так что теперь у меня есть другой экземпляр, указывающий на тот же адрес.

Предположим, исключениепроисходит в функции division выше.Предположим, копия экземпляра выполнена.При возврате из функции исходный экземпляр уничтожается, потому что он находится в памяти стека, поэтому вызывается деструктор и указатель будет освобожден.Однако память указателя также разделяется новой копией.

Когда копия попытается использовать свой указатель, возникнет ошибка, поскольку она не является действительным указателем.В моем случае он использовался деструктором, который пытался free его, но он уже был освобожден: отсюда ошибка.

Правило 5

Проблема возникла из-за моего нарушенияправило 5 в C ++ 11 (ранее правило 3).

Правило гласит, что когда ресурс используется и управляется вручную (в моем случае память была ресурсом) и если класс реализует одиниз следующих членов также должны быть переопределены другие:

  1. деструктор
  2. конструктор копирования
  3. оператор назначения копирования
  4. конструктор перемещения копии
  5. оператор присвоения ходов

4 и 5 соответствуют 2 и 3, но с rvalues ( здесь хороший пост на lvalues rvalues)

Возможные решения

Самый простой способ - использовать string, как уже указано в вопросе, комментариях и ответах, так что память автоматически управляется строковым классом.

Другая альтернатива (как опубликовано в другойответ) заключается в использовании общего указателя.

Хотя неудобно, чтобы быть согласованным с вопросом, здесь реализация, использующая чистые указатели.Конструкторы и деструктор переопределяются так, чтобы им выделялась новая память в конструкторах копирования, вместо этого использовалась память, выделенная другим экземпляром.

/* Define the exception here */
class BadLengthException: public exception
{
  public:
    BadLengthException(int strLength)
    {
        cout<<"Constructor\n";
        strLen = strLength;
        res = (char*)malloc(strLen+1);
        int resultSize = sprintf(res, "%d", strLen);
    }


    /*assignment operator*/
    BadLengthException& operator= (const BadLengthException &other)
    {
        cout<<"copy assignment constructor"<<endl;
        if(&other == this)
        {
            return *this;
        }
        strLen = other.strLen;
        res = (char*)malloc(strLen+1);
        int resultSize = sprintf(res, "%d", strLen);
        return *this;
    }

    /*copy constructor*/
    BadLengthException(BadLengthException& other)
    {
        cout<<"copy constructor\n";
        *this = other;

    }

    BadLengthException(BadLengthException&& other)
    {
        cout<<"move constructor "<<endl;
        *this = other;
    }


    BadLengthException& operator=(BadLengthException&& other)
    {
      cout<<"move assignment operator"<<endl;
      if(&other == this)
      {
          return *this;
      }
      *this = other;
      return *this;

    }

    /*class destructor*/
    ~BadLengthException() throw()
    {
        cout<<"destructor"<<endl;
        free(res);
    }

    virtual const char* what() const throw()
    {
      return res;
    }
  private:
    int strLen;
    char* res;
};

Пример для проверки класса:

int main(int argc, char *argv[])
{
  {
    cout<<"-----Expecting copy constructor------\n";
    BadLengthException e(10);
    BadLengthException e1(e);
    cout<<"10: "<<e1.what()<<endl;
  }
  cout<<endl<<endl;
  {
    cout<<"-----Expecting copy assignment operator------\n";
    BadLengthException e(10);
    BadLengthException e2 = e;
    cout<<"10: "<<e2.what()<<endl;
  }
  cout<<endl<<endl;
  {
    cout<<"-----move assignment operator------\n";
    BadLengthException e3(1);
    e3 = BadLengthException(33);
  }
  {
    cout<<"-----move constructor------\n";
    BadLengthException e4 = BadLengthException(33);
  }
  {
    cout<<"-----move constructor------\n";
    BadLengthException e5(BadLengthException(33));
  }
  cout<<"-----6------\n";
  BadLengthException e6(1), e6_1(2);
  e6 = std::move(e6_1);
  return 0;
}
0 голосов
/ 01 марта 2019

Исключения должны сами по себе не приводить к исключениям;они должны быть noexcept.Поэтому динамическая память alloc / dealloc - либо неявная (например, с использованием std :: string ...), либо явно (new / delete, malloc / free ...) - обычно не рекомендуется.Один из лучших подходов - использовать статически-массив символов:

class BadLengthException:
    public std::exception
{
public:
    BadLengthException(size_t len){
        std::snprintf(res, bufsz, fmt(), len);
    };
    ~BadLengthException()=default;
    virtual const char* what() const {return res;};
private:
    static auto& fmt() {return "bad length [%dz]";};
    static constexpr size_t fmtsz=sizeof(fmt());
    static constexpr size_t intsz=std::numeric_limits<size_t>::digits10;
    static constexpr size_t bufsz=fmtsz+intsz;
    char res[bufsz];
};
0 голосов
/ 05 февраля 2019

Это не имеет ничего общего с исключениями.Ваш класс не является безопасным для копирования.

Если вы собираетесь написать такой класс, то вам нужно заставить его следовать правилу из трех .

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

Но простой способ сделать это - использовать std::string вместо выделения собственной памяти.

class BadLengthException: public exception
{
public:
    BadLengthException(int strLength) : strLen(strLength), res(std::to_string(strLength))
    {
    }
    virtual const char* what() const throw()
    {
      return res.c_str();
    }
  private:
    int strLen;
    std::string res;
};
...