Закрытие соединения и удаление из программы векторных остановок - PullRequest
0 голосов
/ 13 апреля 2020

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

Я попытался поместить операторы печати вокруг строки стирания и никогда не делает это через. Я попытался использовать отладчик, чтобы помочь; отладчик не обнаруживает ничего брошенного. Добавление точки останова перед стиранием позволило программе работать нормально, поэтому я решил, что сон должен иметь тот же эффект, но во время сна он все еще останавливается.

Вот где я пытаюсь закрыть и удалить:

Обратите внимание, что expired_connections - это вектор из outgoing_connections итераторов, помеченных для удаления.

outgoing_connection_mutex.lock();
for (int i = 0; i < expired_connections.size(); i++) {
    auto e = expired_connections[i] - i;
    std::cout << "Closing " << std::get<1>(*e) << std::endl;
    *std::get<3>(*e) = !*std::get<3>(*e);
    if (!close(std::get<1>(*e))) {
        outgoing_connections.erase(e);  // <--- HALTS HERE
    }
}
outgoing_connection_mutex.unlock();

Вот формат outgoing_connections:

std::vector<std::tuple<std::future<std::string>, int, std::chrono::time_point<high_resolution_clock>, std::shared_ptr<bool> > > outgoing_connections;  // Connection future, socket, timeout timer, needs to exists

1 Ответ

1 голос
/ 14 апреля 2020
auto e = expired_connections[i] - i;

Если вам нужно вычесть смещение, то вы удалили итератор перед - но это означает, что все последующие итераторы были признаны недействительными! Вы не можете использовать их больше, не вызывая неопределенное поведение!

Теперь давайте предположим, что будет действительным. Тогда у вас все еще есть другая проблема:

outgoing_connections.push_back(makeConnection());
outgoing_connections.push_back(makeConnection());
// now for any reason, second connection expires before first one!!!
expired_connections.push_back(outgoing_connections.begin() + 1);
expired_connections.push_back(outgoing_connections.begin());

Вы бы сейчас попытались стереть outgoing_connections.begin() - 1! Опять неопределенное поведение.

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

Лучшее, что вы можете сделать, это сбросить этот expired_connections вектор и вместо этого отметить, что сами соединения истекли:

std::vector<std::tuple<
    std::future<std::string>,                       // Connection future
    int,                                            // socket
    std::chrono::time_point<high_resolution_clock>, // timeout timer
    std::shared_ptr<bool>                           // needs to exist
    bool                                            // expired
> outgoing_connections;

(Честно говоря, отдельный класс / struct с именованными членами будет лучшим выбором!)

Тогда вы можете использовать erase-remove-idiom в сочетании с std::remove_if для сброса закрытых соединений:

outgoing_connections.erase
(
    std::remove_if
    (
        outgoing_connections.begin(), outgoing_connections.end(),
        [](auto& connection)
        {
            // minimalistic variant, if need be add output back again...
            return connection.isExpired && !close(connection.fd);
        };
    ), // returns an iterator to new end
    outgoing_connections.end()
);

Преимущество заключается в том, что std::remove_if (а также std::remove) начинают удаление впереди вектора и перемещают все элементы, которые должны оставаться впереди - возможно, пропуская несколько позиций одновременно, поэтому каждый элемент переместился только один раз, ниже того, как эта функция могла бы быть реализована:

template <typename Iterator, typename Condition>
Iterator remove_if(Iterator begin, Iterator end, Condition condition)
{
    Iterator pos = begin;
    for(; begin != end; ++begin)
    {
        if(!condition)
        {
            // element shall remain!
            if(begin != pos)
                *pos++ = *begin;
        }
    }
    return pos;
}

Вы видите: Каждый элемент касается только один раз ... Пока, однако, элементы перемещаются только (или копируется, если не доступно перемещение), но лишние элементы в конце не удаляются. Вот почему вам все еще нужен звонок на erase.

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