#c
#c
Вопрос:
Я определил следующую структуру данных, целью которой является предоставление метода get_next(), который возвращает итератор, указывающий на следующую требуемую конфигурацию. Если required_explorations
пусто или достиг конца, он возвращает его, указывая на начало.
#include <iostream>
#include <map>
#include <string>
#include <unordered_map>
using configuration_model = std::unordered_map<std::string, std::string>;
struct doe_model {
inline bool add_config(const std::stringamp; config_id,
const configuration_modelamp; config,
const int required_number_of_observations)
{
bool assignment_took_place = !required_explorations.insert_or_assign(config_id, config).second || !number_of_explorations.insert_or_assign(config_id, required_number_of_observations).second;
next = required_explorations.end();
return assignment_took_place;
}
inline void update_config(const std::stringamp; config_id)
{
number_of_explorations.at(config_id)--;
// remove the configuration in case we exausted all the explorations
if (number_of_explorations.at(config_id) <= 0)
remove_config(config_id);
}
inline void remove_config(const std::stringamp; config_id)
{
required_explorations.erase(config_id);
number_of_explorations.erase(config_id);
}
// this method returns the next configuration to explore
// NOTE: the caller MUST check the pointer first.
inline std::map<std::string, configuration_model>::iterator get_next()
{
// we may have an empty map or one with only a single configuration left
if (required_explorations.empty() || next == required_explorations.end()) {
std::cout << "Required explorations is empty or next at the end(), "
"returning the first pointer."
<< std::endl;
next = required_explorations.begin();
return next;
}
next ;
std::cout << "Returning a next pointer normally." << std::endl;
return next;
}
// key is the configuration_id
std::map<std::string, configuration_model> required_explorations;
std::map<std::string, configuration_model>::iterator next;
std::map<std::string, int> number_of_explorations;
};
Я не настолько осведомлен о мелкой копии по умолчанию, что она генерируется для оператора присваивания с этой структурой, но, тестируя ее, я думаю, что проблема зависит от next
копии итератора.
Выполнение этого возвращает 0
ожидаемый результат (который является первым элементом).
int main()
{
doe_model doe;
doe.add_config("0", { { "threads", "29" } }, 4);
doe.add_config("1", { { "threads", "23" } }, 4);
doe.add_config("2", { { "threads", "20" } }, 4);
doe.add_config("3", { { "threads", "21" } }, 4);
doe.add_config("4", { { "threads", "22" } }, 4);
auto it = doe.get_next();
std::cout << it->first << "t" << std::endl;
}
Но выполнение следующего возвращает 4
, что является концом.
int main()
{
doe_model doe;
doe.add_config("0", { { "threads", "29" } }, 4);
doe.add_config("1", { { "threads", "23" } }, 4);
doe.add_config("2", { { "threads", "20" } }, 4);
doe.add_config("3", { { "threads", "21" } }, 4);
doe.add_config("4", { { "threads", "22" } }, 4);
doe_model new_doe = doe;
auto it = new_doe.get_next();
std::cout << it->first << "t" << std::endl;
}
Может кто-нибудь дать мне некоторое представление о том, что происходит за кулисами?
ОБНОВЛЕНИЕ вот реальный пример doe_model
использования. Есть некоторые элементы данных и другие вызовы функций, о которых я не собираюсь говорить, поскольку они на самом деле не нужны для понимания того, как doe_model
предполагается использовать. При send_configuration
вызове ему необходимо извлечь новую конфигурацию из структуры doe, и для get_next
этого необходим метод для выполнения этой задачи, возвращающий новую конфигурацию (отличную от предыдущей и которая все еще number_of_explorations > 0
существует).
void send_configuration(const client_id_t amp;name)
{
if (num_configurations_sent_per_iteration <= num_configurations_per_iteration)
{
auto configuration = doe.get_next();
if (configuration != doe.required_explorations.end())
{
remote->send_message(
{MESSAGE_HEADER "/" app_id.app_name "^" app_id.version "^" app_id.block_name "/" name "/explore",
configuration_to_json(configuration->second)});
doe.update_config(configuration->first);
num_configurations_sent_per_iteration ;
}
}
}
Комментарии:
1. При копировании копируется
doe
базовая карта, итератор также копируется, но он указывает наdoe.required_explorations
, нетnew_doe.required_explorations
.
Ответ №1:
Элемент next;
копируется, по-прежнему ссылаясь на другой экземпляр required_explorations
.
При вызове get_next()
сравнение next == required_explorations.end()
имеет неопределенное поведение, оно определено только для итераторов в том же контейнере 1.
В стороне: в случае, когда required_explorations
пусто, возврат required_explorations.begin()
и разыменование также имеют неопределенное поведение.
- Если вы используете C 14 или более позднюю версию, вы можете сравнить свой итератор с итератором, инициализированным значением, который ведет себя
end
одинаково для всех контейнеров. В C 11 вам нужен второй итератор из контейнера, чтобы быть в безопасности.
Комментарии:
1. О, я понимаю.. Таким образом, определение пользовательского конструктора копирования и оператора присваивания должно быть правильным направлением, я полагаю. Или есть что-то проще, что можно сделать?
2. @Barnercart К сожалению, да, в общем случае. Или вообще избавиться от итераторов / ссылок.
3. @Barnercart Я бы посоветовал избавиться от
next
иget_next
, заменив наbegin
/end
, который раскрывает картуbegin
/end
4. @Caleth спасибо за совет. Я все равно планировал включить это в обзоры кода, как только это было bugs / unexpected_behaviours бесплатно.
5. @Caleth Я думал о вашем предложении, но мне нужно перебирать карту извне, и поскольку мне не нужно дважды обращаться к одному и тому же элементу, мне нужен метод (
next
), который дает мне возможность всегда обращаться к другому элементу (за исключением случаев, когда, конечно, есть только один элемент). Раскрытиеbegin/end
не позволяет мне выбрать следующий элемент в «очереди» со стороны вызывающей стороны.