std:: ошибка / проблема priority_queue в конфигурации отладки MSVC 2008/2010

#c #visual-c

#c #visual-c

Вопрос:

Вкратце, мой вопрос несколько связан с языком: является ли следующая проблема ошибкой в MSVC или «приемлемым» поведением?

После обнаружения неприятного нарушения доступа в чьем-то коде я отследил источник проблемы до реализации отладки std::priority_queue<> в MSVS. (Я проверил, что проблема существует как в 2008, так и в 2010 годах.) Ниже приведен сокращенный пример кода, иллюстрирующий проблему:

 #include <queue>
#include <iostream>

struct Less_than
{
    bool operator() (const int *x, const int *y) const
    {
        std::cout << "Less_than(" << *x << ", " << *y << ")" << std::endl;
        return *x < *y;
    }
};

int main()
{
    std::priority_queue<int *, std::vector<int *>, Less_than> q;
    int x = 0;
    int y = 1;
    q.push(amp;x);
    q.push(amp;y);
    std::cout << "Popping " << *q.top() << "..." << std::endl;
    q.pop();
}
  

Вывод должен быть (и есть в конфигурации сборки релиза):

 Less_than(0, 1)
Popping 1...
  

Однако в конфигурации сборки Debug вывод:

 Less_than(0, 1)
Less_than(1, 0)
Popping 1...
Less_than(1, 0)
  

Ключевым отличием является окончательное дополнительное сравнение, которое происходит как побочный эффект pop(). В отладочной сборке, когда pop () извлекает верхний элемент из очереди, реализация pop () сначала сравнивает верхний элемент с любым другим элементом в очереди, чтобы убедиться, что это действительно максимальный элемент.

Это, очевидно, было бы проблемой (и было проблемой в рассматриваемом коде), если бы, как в приведенном выше примере, вы использовали priority_queue указателей на объекты с вашим собственным сравнением… но также были удалены объекты со следующим:

 delete pq.top();
pq.pop();
  

Здесь мы освобождаем объект, на который указывает pq.top(), но затем, когда мы пытаемся pop () указатель из очереди, отладочная реализация pop () сначала пытается сравнить недействительный объект, на который указывает top(), со всем остальным в очереди!

Конечно, простым решением является простая замена приведенного выше на:

 T *p = pq.top();
pq.pop();
delete p;
  

Но мой вопрос таков: технически, разве исходный код не должен также работать? Говоря точнее, должны ли мы быть в состоянии зависеть от pop (), не сравнивая текущий верхний элемент с чем-либо в процессе его удаления? Я прочитал, что стандарт здесь довольно точен; соответствующими разделами C 03 являются 25.2.3.2.2 ( pop() ) и 25.3.6.2 ( pop_heap() ).

Заранее спасибо!

Комментарии:

1. С какой стати вы используете int* здесь? Это путь к ошибкам. Один из которых вы, вероятно, приготовили здесь.

2. Это всего лишь пример, демонстрирующий проблему. В более общем плане проблема заключается в том, что у вас есть priority_queue<T *> , где T быть чем-то большим, чем просто int , не так уж и надуманно.

Ответ №1:

Я бы сказал, что ваш код T *p = pq.top(); неверен. Вы получаете значение const amp; для элемента в вашей очереди. Но вы удаляете полученный объект. Итак, по сути, вы сделали объект в своей очереди недействительным.
Так что это опасная вещь. Я бы избегал делать такие вещи.

Комментарии:

1. Обратите внимание, что очередь не содержит «объект» (т. Е. экземпляр типа T ), она содержит указатель на объект. Итак, вы правы, при удалении перед появлением очередь теперь содержит недопустимый указатель . Но мой вопрос не об опасности или уместности, а о том, выполняет ли компилятор что-то, что стандарт явно / неявно запрещает.

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

3. Согласен. Но это не отвечает на мой вопрос. Вопрос заключался в том, запрещает ли стандарт. Обратите внимание, например, что время выполнения O (lg n) pop указано явно.

4. @possiblywrong: Я был немного поверхностен в отношении объекта и указателя, извините за это. Но я просмотрел стандарт и не нашел ничего, что прямо запрещало бы такое поведение, но я предполагаю, что вы сделали то же самое, и я не стандартный умник. Итак, судя по тому, как я прочитал стандарт, поведение в порядке. Но я согласен, что это интересно узнать.