С++ выдает исключение std::bad_alloc для очень маленького std::vector с использованием std::sort

Я работаю над проектом на С++, который имеет дело с данными, разделенными запятыми (CSV). Что я делаю, так это читаю данные из файла .csv в вектор объектов CsvRow.
Итак, сегодня я столкнулся с очень странными исключениями std::bad_alloc, возникающими в гораздо более странных ситуациях. А именно, первым тестовым случаем, в котором мне удалось получить немного больше времени, пока я не сгенерирую исключение, было чтение всего файла csv в вектор. Файл состоит из 500 000 строк и имеет размер около 70 МБ. Файл был прочитан в память как шарм, но затем, через несколько секунд в процедуре сортировки, выбрасывается std::bad_alloc. Он использовал примерно 67 МБ ОЗУ. Примечание. Я использую облегченные веса Boost, чтобы уменьшить потребление памяти.

НО, этот тестовый пример был еще более странным: я читаю файл размером 146 КБ с несколькими сотнями строк, и на этот раз я получил исключение при чтении данных в вектор, что совершенно нелепо, когда ранее было успешно прочитано 70 МБ.

Я подозреваю утечку памяти, но моя машина имеет 8 ГБ ОЗУ и использует 64-разрядную версию Windows 8. Я использую CodeBlocks и 64-разрядный дистрибутив MinGW Boost. Любая помощь будет оценена по достоинству. Вот кусок кода, в котором выбрасывается std::bad_alloc:

  1. Чтение данных из файла csv

    std::ifstream file(file_name_);
    int k=0;
    for (CsvIterator it(file); it != CsvIterator(); ++it) {
    
        if(columns_ == 0) {
            columns_ = (*it).size();
            for (unsigned int i=0; i<columns_; i++) {
                 distinct_values_.push_back(*new __gnu_cxx::hash_set<std::string,                         
                                            std::hash<std::string> >());
            }
        }
    
        for (unsigned int i=0; i<columns_; i++) {
            distinct_values_[i].insert((*it)[i]);
        }
    
        all_rows_[k]=(*it);
        k++;
    }
    
  2. Сортировка вектора с использованием внутренней структуры, хранящейся в моем классе

    struct SortRowsStruct
    {
        CsvSorter* r;
        SortRowsStruct(CsvSorter* rr) : r(rr) { };
    
        bool operator() (CsvRow a, CsvRow b)
        {
            for (unsigned int i=0; i<a.size(); i++) {
                if(a[r->sorting_order_[i]] != b[r->sorting_order_[i]]) {
                    int dir = r->sorting_direction_[i];
                    switch(dir) {
                        case 0:
                            return (a[r->sorting_order_[i]] < b[r->sorting_order_[i]]);
                            break;
                        case 1:
                            return !(a[r->sorting_order_[i]] < b[r-    >sorting_order_[i]]);
                            break;
                        case 2:
                            return true;
                            break;
                        default:
                            return true;
                    }    
                }
            }
            return true;
        }
     }; 
    

Затем я использую std::sort() для сортировки вектора CsvRows.

SortRowsStruct s(this);
std::sort(all_rows_.begin(), all_rows_.end(), s);

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

distinct_values_.push_back( *new __gnu_cxx::hash_set<std::string,                                     
                             std::hash<std::string> >() ); 

Удаление этих хеш-наборов в деструкторе приводит к сбою программы (SIGSEGV). О, и еще одна вещь, на которую следует обратить внимание, это то, что я не могу использовать 32-битный отладчик gdb по умолчанию из-за того, что мой MinGW 64-битный. 32-битный gdb содержит ошибки и не будет работать с MinGW 64.

Изменить:
Может ли
boost::flyweight<std::string> использовать в классе CsvRow проблему?

Кроме того, вот часть класса CsvRow:

private:
    std::vector<boost::flyweights::flyweight<std::string> > row_data_;

И перегруженный оператор [] в классе CsvRow:

std::string const& CsvRow::operator[](std::size_t index) const
{
    boost::flyweights::flyweight<std::string> fly = row_data_[index];
    return fly.get();
}

заранее спасибо

РЕДАКТИРОВАТЬ - РЕШЕНО: Итак, этот вопрос решил мою проблему, хотя я даже не думал об этом. Каждый пользовательский компаратор, который мы передаем в std::sort(), должен быть строго слабым порядком, то есть:
1. Нерефлексивный
2. Асимметричный
3. Транзитивный
4. Транзитивность несравнимости

Дополнительные сведения см. по адресу: Этот вопрос и Эта статья Wiki
На самом деле, я не следил за первым ( иррефлексивность), то есть, если оба объекта CsvRow равны, он не должен "сравнивать" их и возвращать true, как если бы они были в порядке, а вместо этого возвращать false. Я решил всю проблему, только изменив возвращаемое значение по умолчанию, когда оба CsvRow a и CsvRow b равны.

bool operator() (CsvRow a, CsvRow b)
{
    for (unsigned int i=0; i<a.size(); i++) {
        if(a[r->sorting_order_[i]] != b[r->sorting_order_[i]]) {
            ...
            ...
        }
    }
    return false;  //this line does not violate the irreflexivity rule
    //return true;   //but this one does
}

Спасибо всем, кто пытался помочь. Запомните это решение на случай, если у вас возникнет аналогичная проблема. Это довольно сложно.


person Nino    schedule 30.11.2013    source источник
comment
Опубликуйте заявление distinct_values_, и я могу почти гарантировать, что вы правы в том, что эта строка абсолютно отвратительна. Когда вы говорите Удаление этих хеш-наборов в деструкторе, происходит сбой программы.. - в деструкторе чего?? Из того, что я вижу, они не должны быть динамически распределены вообще.   -  person WhozCraig    schedule 30.11.2013
comment
Да, строка, которую вы выделяете как подозрительную, выглядит как полная утечка памяти. Вы new что-то разыменовываете указатель на то, что вы только что выделили, а затем копируете инициализированный объект в другой новый объект, выделенный vector::push_back(). Вы никогда не сохраняете указатель, возвращенный new, поэтому вы никак не можете его delete. Тем временем push_back() создает новые объекты в вашем vector, в которые вы просто копируете. Если вы попытаетесь delete эти объекты, вам будет больно; они являются частью хранилища, которым управляет vector.   -  person Joe Z    schedule 30.11.2013
comment
FWIW, вы сможете полностью удалить четыре символа *new. Что происходит, когда вы пишете: distinct_values_.push_back( __gnu_cxx::hash_set<std::string, std::hash<std::string> >() );   -  person Joe Z    schedule 30.11.2013
comment
@WhozCraig Объявление distinct_values_ равно std::vector<__gnu_cxx::hash_set<std::string, std::hash<std::string> > > distinct_values_;, которое представляет собой вектор hash_set, который мне нужен для каждого столбца. Я попытался удалить сегмент *new, а также удалил строки удаления в деструкторе класса, в котором они содержатся.   -  person Nino    schedule 30.11.2013
comment
@JoeZ: Я только что попробовал ваше предложение, но снова происходит то же самое. Он считывает данные, а затем во время std::sort я получаю код выхода 0xFF.   -  person Nino    schedule 30.11.2013
comment
Все эти добавленные правки затрудняют понимание вашего вопроса.   -  person Jamal    schedule 08.09.2015


Ответы (1)


Этот:

distinct_values_.push_back( *new __gnu_cxx::hash_set<std::string,                                     
                            std::hash<std::string> >() );

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

distinct_values_.resize(distinct_values_.size() + 1);

Помимо того, что его легче набирать и он более общий, он также гораздо более правильный: мы не должны ничего здесь newировать, просто создавать одно значение в конце, и мы должны позволить вектору построить его, а не копировать его, что может быть расточительным.

И, конечно, мы никогда не должны пытаться delete эти значения.

person John Zwinck    schedule 30.11.2013
comment
Предполагая, что distinct_values является вектором, я подозреваю, что это приводит к квадратичному поведению, а не к линейному поведению, которое гарантирует push_back. - person Alan Stokes; 30.11.2013
comment
@AlanStokes: с чего ты это взял? Оба способа будут иметь одинаковую производительность. Изменение размера вектора по одному делает обычное двойное действие, когда это необходимо. - person John Zwinck; 30.11.2013
comment
Я пытался использовать .resize(), но программа вылетает, возвращая 0xFF. Однако квадратичное поведение не имеет большого значения в моем случае из-за значительно небольшого количества hash_set объектов, которые мне нужно добавить (не более 50). Тестовый пример, который меня интересует, использует только 2 hash_sets. - person Nino; 30.11.2013
comment
Нет квадратичного поведения при изменении размера. Пожалуйста, запустите вашу программу в отладчике и покажите нам код, в котором она сейчас дает сбой. - person John Zwinck; 30.11.2013
comment
@Джон А, ты прав, извини, все в порядке. Я думал о коде, который я видел, который неоднократно использовал reserve для увеличения емкости перед вставкой, что действительно ведет себя очень плохо. - person Alan Stokes; 30.11.2013
comment
@JohnZwinck Думаю, я понял это. Вероятно, это пользовательская структура сравнения, которая выдает std::bad_alloc. Я только что наткнулся на вопрос, касающийся этой проблемы, и парень, который ее решил, предложил несколько правил, которым должен следовать этот пользовательский компаратор. Я просто дважды проверю это сейчас. - person Nino; 30.11.2013
comment
В C++11 есть еще более простой способ: distinct_values_.emplace_back(); - person SoapBox; 30.11.2013