Не нарушает ли очистка std :: queue с помощью scoped queue :: swap какие-либо правила? - PullRequest
6 голосов
/ 17 мая 2019

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

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

#include <cstdlib>
#include <iostream>
#include <queue>
int
main()
{
    std::queue<int> foo;
    foo.push(10);
    foo.push(20);
    foo.push(30);
    std::cout << "size of before foo: " << foo.size() << '\n';

    {
        std::queue<int> bar;
        swap(foo, bar);
    }
    std::cout << "size of after foo: " << foo.size() << '\n';
    return 0;
}

Ответы [ 5 ]

5 голосов
/ 17 мая 2019

Ваш код в порядке.swap сделает foo созданным по умолчанию std::queue, и когда bar будет уничтожен в конце области, он освободит память, использованную foo.Поскольку вы не используете new или delete, это не проблема, поскольку std::queue "делает правильные вещи" ( RAII типы - замечательная вещь)

Эффективно вы 'Вы сделали

std::queue<int>{std::move(foo)}; // move foo into a temporary that is immediately destroyed to release the storage

, но ваш метод дает вам более сильную гарантию о состоянии foo.Ваш метод оставляет foo в состоянии по умолчанию, а вышеприведенный метод оставляет его в допустимом, но неопределенном состоянии.


Другой вариант - использовать одно из решений, представленных в IsЕсть ли способ получить доступ к нижележащему контейнеру контейнерных адаптеров STL? , чтобы получить нижележащий контейнер из foo и вызвать для него команду clear.Это будет выглядеть как

#include <cstdlib>
#include <iostream>
#include <queue>

// function from https://stackoverflow.com/a/29325258/4342498 by jxh: https://stackoverflow.com/users/315052
template <class ADAPTER>
typename ADAPTER::container_type & get_container (ADAPTER &a)
{
    struct hack : ADAPTER {
        static typename ADAPTER::container_type & get (ADAPTER &a) {
            return a.*&hack::c;
        }
    };
    return hack::get(a);
}

int main()
{
    std::queue<int> foo;
    foo.push(10);
    foo.push(20);
    foo.push(30);
    std::cout << "size of before foo: " << foo.size() << '\n';

    get_container(foo).clear();

    std::cout << "size of after foo: " << foo.size() << '\n';
    return 0;
}
2 голосов
/ 17 мая 2019

Это не только совершенно безопасно, но и то, как обычно работают конструкторы перемещения для контейнеров: путем замены недолговечным (или, по крайней мере, вскоре уничтожаемым) другим объектом, после чего другой объект умирает.Затем деструктор выполняет всю очистку данных для вас как можно быстрее.(Здесь это обходится из-за отсутствия clear() функции-члена.)

Я считаю, что мне нужна была операция «очистки» одним выстрелом, и я действительно хотел использовать очередь (например, для форсирования FIFO, напримервы сказали) тогда я бы сделал то же самое.

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

1 голос
/ 17 мая 2019

Нет необходимости в области действия:

std::queue<int>().swap(foo);

, но в противном случае ваш код действителен.

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

Затем в конце оператора мы уничтожаем временную очередь вместе со старым состоянием foo.

Существует также краткий, который работает практически со всеми типами:

template<class T>
void clear( T& t ) {
  using std::swap;
  swap( static_cast<T&>(T()), t );
}

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

Объекты, которые не могут быть построены по умолчанию (), не смогут скомпилироваться здесь.

1 голос
/ 17 мая 2019

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

И я бы сказал, что замена очереди (или любого другого контейнера в этом отношении) на временный объект является предпочтительным способом очистки контейнера в контексте с поддержкой потоков:

void MyClass::Reset()
{
    // ...
    {
        Container      tmp;
        {
            std::scoped_lock     lock(m_mutex);
            tmp.swap(m_container);
        }
    }
    // ...
}

Причины,

  1. Более короткая критическая секция. Поскольку блокировка синхронизации должна быть получена только на время операции свопа, которая довольно коротка.
  2. Уменьшена область действия для взаимоблокировок. Поскольку деструкторы содержащихся объектов вызываются после снятия блокировки. Но это, конечно, зависит от того, что эти деструкторы могут назвать.
1 голос
/ 17 мая 2019

Не должно нарушать никаких правил, но вы можете просто сделать

foo = std::queue<int>{};

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