Когда следует использовать const_iterator вместо auto - PullRequest
0 голосов
/ 04 мая 2018

Ниже приведен пример, который, я думаю, иллюстрирует случай, когда предпочтительнее использовать const_iterator для "const auto". Это связано с тем, что контейнеры не предоставляют функцию cfind (). Есть ли альтернатива? Или следует использовать «const auto» и игнорировать отсутствие const?

std::string GetJobTitle(const std::string& employee)
{
    using EmployeeTitles = std::unordered_map<std::string, std::string>;
    EmployeeTitles employees = { { "Alice", "Director" },{ "Bob","Manager" } ,{ "Charlie", "Developer" } };

    // Option 1. const_iterator is access only:
    EmployeeTitles::const_iterator itr = employees.(employee);
    if (itr != employees.cend())
    {
        itr->second = "Project Manager"; // error C2678: The compiler prevents us changing the job tile which is what we want
        return itr->second;
    }

    // Option 2. const auto is more concise but is not as safe:
    const auto& itr2 = employees.find(employee);
    if (itr2 != employees.cend())
    {
        itr2->second = "Project Manager"; // employee now has new title - how can we prevent this with the compiler but still use auto?
        return itr2->second;
    }
    return "";
}

1 Ответ

0 голосов
/ 04 мая 2018

Если возможно, обойти проблему

Использование const переменных

Ваш пример не иллюстрирует хорошее обоснование проблемы. Просто будьте более «агрессивными» с постоянством, и оно исчезнет. Вы вообще не меняете employees, поэтому правильное решение - объявить его const в первую очередь:

const EmployeeTitles employees = ...;

Это даже безопаснее, потому что он предотвращает изменения в employees где угодно, а не только через итератор.

Используйте область видимости для разделения постоянного / неконстантного кода

Что если вы не можете сделать employees const, потому что вы можете заполнять его только по частям; например, потому что вы извлекаете информацию из базы данных? Переместите заполненный код в функцию конструктора. Или для простых случаев используйте немедленно вызванную лямбду:

const EmployeeTitles employees = [] {
    EmployeeTitles employees;

    for (const auto& record : database.employees()) {
        // Probably some more processing would be done here in the real world.
        employees.emplace(record.name(), record.job_title());
    }

    return employees;
}();

Использование const функций-членов

Если employees является переменной-членом класса и вы перебираете ее в функции-члене, сделайте эту функцию const.

Как правило

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

Что если вы наткнетесь на него?

В этом случае я бы выбрал вариант 1: Явное объявление итератора const_iterator в сочетании с объявлением using для типа карты. Это сжато, читабельно, сразу понятно и самый прямой способ выразить свое намерение.

Другие решения, которые манипулируют с константой employees, не так хороши, потому что это не то, что вас действительно волнует. То, что вы на самом деле хотите, это итератор только для чтения. Игра с константой employees - это всего лишь окольный путь к достижению этой цели. И окольный код сложнее понять.

С другой стороны, это не значит, что у вас возникнут огромные проблемы с ясностью. Особенно std::as_const приятно и лаконично.

Однако при использовании кодовой базы до C ++ 17 вам придется использовать const_cast. Это доброкачественный, потому что добавляет const, и это не слишком многословно. Но я бы избегал этого по общему принципу, что на первый взгляд видеть const_cast в куске кода всегда немного страшно. Другая хорошая возможность, как отметил @Swift в комментариях, - реализовать собственную версию as_const.

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