Использование пользовательских типов с std :: vector и std :: sort - PullRequest
1 голос
/ 03 февраля 2020

Я пытался разобраться с использованием пользовательских типов (или классов) со стандартными контейнерами библиотеки C ++ - векторами. Я хочу иметь возможность делать обычные вещи, которые мне удобно делать с векторами встроенных типов C ++, int, float, string и т. Д. c .. но с моими собственными определенными типами. Я написал небольшой пример программы, использующей класс box, чтобы попытаться понять, что происходит.

Вот код для класса:

class box {

private:
    float *lengthPtr;
    std::string *widthPtr;

public:

    box(float a, std::string b) {
        std::cout<<"Constructor called" << '\n';
        lengthPtr = new float;
        *lengthPtr = a;

        widthPtr = new std::string;
        *widthPtr = b;     
    } 
    // copy constructor 
    box(const box &obj){
        std::cout<< "User defined copy constructor called" << '\n';
        lengthPtr = new float;
        *lengthPtr = obj.check_length();

        widthPtr = new std::string;
        *widthPtr = obj.check_width();

    }
    // copy assignment operator
    box& operator=(const box &that) {
        std::cout<< "Copy assignment operator called";            

            float *localLen = new float;
            *localLen = that.check_length(); 
            delete[] lengthPtr; 
            lengthPtr = localLen;

            std::string *localWid = new std::string;
            *localWid = that.check_width();
            delete[] widthPtr;
            widthPtr = localWid;            

        return *this;
    }

    ~box() {
        std::cout << "User defined destructor called." << '\n';
        delete lengthPtr;
        delete widthPtr;
    }

    float check_length () const {
        return *lengthPtr;
    }

    std::string check_width() const{
        return *widthPtr;
    }

    void set_legnth(const float len) {
        *lengthPtr = len;
    }
    void set_width(const std::string str) {
        *widthPtr = str;      
    }

    void print_box_info(){  
        std::cout << *lengthPtr << " " << *widthPtr << '\n';
    }
};

Две основные вещи sh Чтобы иметь возможность сделать следующее:

  1. Добавьте произвольное число новых элементов моего пользовательского типа (box) к вектору, используя .push_back().

  2. После сохранения своих элементов я хочу отсортировать их, используя std::sort с определенной пользователем функцией сравнения.

Это основная функция, которую я использовал для проверки моих 2 целей:

int main() {
    srand(time(NULL));
    int i = 0;
    std::vector<box> boxes;

    while (i<25) {
        int x = rand()%100+1;
        std::cout<< "x = " << x << '\n';

        if ( i < 5)        
            boxes.push_back(box(x, "name"));
        if ( i > 4 && i < 12)
            boxes.push_back(box(x, "Agg"));
        if ( i > 11 && i < 20 )
            boxes.push_back(box(x, "Cragg"));
        if (i>19)
            boxes.push_back(box(x, "Lagg"));

        std::cout << "Added the new box to the collection." << '\n';

        i++;  
    }
    for(unsigned int j = 0; j<boxes.size(); j++) {
            boxes[j].print_box_info();
    }
    std::sort(boxes.begin(), boxes.end(), type_is_less);
}

Код, который я написал до сих пор, кажется способным выполнить задачу 1. После запуска программы, для l oop через некоторое время l oop печатает информацию о 25 ящиках, хранящихся в векторе моих ящиков. Однако, когда я пытаюсь отсортировать свои ящики, используя std::sort и функцию type_is_less():

bool type_is_less(const box &a, const box &b) {
    std::cout<<"In type is less." << '\n';
    std::string A = a.check_width();
    std::string B = b.check_width();

    std::cout<< "Comparing box a, width = "  << A << '\n';
    std::cout<< "with box b, width = " << B << '\n'; 
    bool val = A<B;
    std::cout << "Returning " << val <<'\n' <<'\n'; 
    return A<B; 
}

, я получаю ошибку сегментации, но я не уверен, откуда исходит ошибка. Определяемый пользователем конструктор копирования представляется последней функцией, вызываемой до возникновения ошибки сегмента. Кажется, конструктор копирования можно использовать в push_back(), но это вызывает проблемы в std::sort?

Я попытался отладить конструктор копирования с сообщениями std::cout между каждой строкой и каждой строкой копии. Конструктор, кажется, выполняется, не вызывая ошибку сегмента. Ошибка seg появляется, как только завершается выполнение конструктора копирования. Конец моей консоли выводится ниже (// я вставил комментарии, используя '//'):

Добавлен новый блок в коллекцию.

3 name

// ...

// ...

// программа печатает 2 информационных точки для каждого блока

61 Lagg // Это окончательная информация о коробке печати. ​​

В типе меньше. Сравнивая поле a, ширина = имя с полем b, ширина = Cragg Возвращает 0

В типе меньше. Сравнивая поле a, ширина = имя с полем b, ширина = Lagg Возвращает 0

В типе меньше. Поле сравнения a, width = Cragg с полем b, width = Lagg Возвращает 1

Определенный пользователем конструктор копирования с именем

Ошибка сегментации (ядро сброшено)

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

Открытый вопрос, который мне еще предстоит выяснить, состоит в том, могу ли я определить класс, аналогичный этому, но с переменными без указателей lengthPtr и widthPtr, и при этом иметь те же функции.

Ответы [ 2 ]

1 голос
/ 03 февраля 2020

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

Но давайте предположим, что вы делаете это для экспериментов: ваше box задание Оператор имеет несколько проблем:

  1. Использование неправильной формы delete... (должно быть delete, а не delete[]).
  2. Нет проверки на назначение box экземпляров.
  3. Если существует проблема с new std::string, создающим исключение, вы повредили свой объект, изменив lengthPtr.

Для 1), исправление: простой и, учитывая вашу тестовую программу, решит проблему cra sh:

  box& operator=(const box& that) {
        std::cout << "Copy assignment operator called";

        float* localLen = new float;
        *localLen = that.check_length();
        delete lengthPtr;  // Correct form of `delete`
        lengthPtr = localLen;

        std::string* localWid = new std::string;
        *localWid = that.check_width();
        delete widthPtr; // Correct form of `delete`
        widthPtr = localWid;

        return *this;
    }

Однако ваш код будет вызывать неопределенное поведение, если самопредставление объектов box было должно быть сделано.

Для 2) перед попыткой воссоздания копии необходимо выполнить проверку:

  box& operator=(const box& that) 
  {
        std::cout << "Copy assignment operator called";

        // check if attempting to assign to myself.  If so, just return
        if ( &that == this )
           return *this;

        float* localLen = new float;
        *localLen = that.check_length();
        delete lengthPtr;  // Correct form of `delete`
        lengthPtr = localLen;

        std::string* localWid = new std::string;
        *localWid = that.check_width();
        delete widthPtr; // Correct form of `delete`
        widthPtr = localWid;

        return *this;
    }

Для 3), обратите внимание, что с помощью new, возможно (даже удаленно) из new с исключением std::bad_alloc. Если это произойдет, и это произойдет в строке new std::string, вы бы испортили свой объект box, поскольку lengthPtr был изменен преждевременно.

Опять же, ваш пример будет очень редким случаем сбоя new, но тот же сценарий может произойти, если, скажем, нам нужно выделить несколько миллионов std::string с помощью одного вызова new std::string [x] .

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

Вот пример:

box& operator=(const box& that) 
{
    std::cout << "Copy assignment operator called";
    if ( &that == this )
       return *this;

    // Allocate everything first
    float* localLen = new float;  // If this throws, we'll exit anyway.  No harm
    std::string* localWid = nullptr;  
    try 
    {
        localWid = new std::string;  // If this throws exception, need to rollback previous allocation and get out
    }
    catch (std::bad_alloc& e)
    {
       delete localLen;  // rollback previous allocation and rethrow
       throw e;
    }

    // Everything is ok, now make changes
    *localLen = that.check_length();
    delete lengthPtr;
    delete widthPtr;
    lengthPtr = localLen;
    widthPtr = localWid;
    return *this;
}

В целом, это много работы для правильно работающего оператора назначения.

Хорошая новость заключается в том, что существует метод, который гораздо проще закодировать, который решает все упомянутые проблемы, если у вас есть конструктор и деструктор рабочей копии. Эта методика - идиома copy / swap :

 box& operator=(const box& that) 
 {
    std::cout << "Copy assignment operator called";
    box temp(that);
    std::swap(lengthPtr, temp.lengthPtr);
    std::swap(widthPtr, temp.widthPtr);
    return *this;
 } 

Нет необходимости в проверке самостоятельного назначения (даже если она может использоваться в целях оптимизации), и нет нужно проверить, выбрасывает ли new, так как на самом деле нет вызовов new (создание temp автоматически выкинет нас, если что-то пойдет не так).

0 голосов
/ 03 февраля 2020

Причина ошибки сегментации заключается в вашей функции box& operator=(const box &that).

Во время отладки я обнаружил эту ошибку -

ERROR: AddressSanitizer: alloc-dealloc-mismatch (operator new vs operator delete [])

lengthPtr и widthPtr не создаются с синтаксисом new[]. Таким образом, когда вы пытаетесь удалить с помощью delete[], вы получаете ошибку сегментации.

Чтобы удалить ошибку сегментации из вашего кода, просто замените delete[] на delete в конструкторе с реализацией оператора присваивания.

Пожалуйста, проверьте этот ответ тоже - delete против операторов delete [] в C ++

...