В проекте, использующем C++20, CLion предложил мне добавить [[nodiscard]]
к моим определениям методов константного класса, например,
class Test {
public:
[[nodiscard]] int f(int a, int b) const {
return a + b;
}
}
Объяснение
Добавляет атрибуты [[nodiscard]] (представленные в C++17) к функциям-членам, чтобы выделить во время компиляции, какие возвращаемые значения не следует игнорировать.
а еще я проверила cppreference.com:
Если функция, объявленная nodiscard, или функция, возвращающая перечисление или класс, объявленный nodiscard по значению, вызывается из выражения отброшенного значения, отличного от приведения к void, компилятору рекомендуется выдавать предупреждение. ... Появляется в объявлении функции, объявлении перечисления или объявлении класса.
Если из выражения отброшенного значения, отличного от приведения к void,
- вызывается функция, объявленная nodiscard, или
- вызывается функция, возвращающая перечисление или класс, объявленный nodiscard по значению, или
- конструктор, объявленный nodiscard, вызывается явным преобразованием типа или static_cast, или
- объект типа перечисления или класса, объявленный nodiscard, инициализируется явным преобразованием типа или static_cast,
компилятору рекомендуется выдавать предупреждение.
Я, честно говоря, не очень понимаю, зачем эта аннотация нужна в этом месте. Почему компилятор проигнорирует мои возвращаемые значения, если вызывающая сторона обрабатывает их дальше? Есть ли интуитивное объяснение того, что именно он сообщает компилятору и зачем это нужно?
Это не компилятор игнорирует результат, это можете быть вы. Если вы пишете Test t; t.f(1, 2);
, вы отбрасываете значение, но это не имеет смысла, потому что функция не делает ничего, кроме возврата значения. Без [[nodiscard]]
это проходит незаметно. С [[nodiscard]]
компилятор выдаст предупреждение, чтобы обратить ваше внимание на этот момент.
@Yksisarvinen В моем случае моя IDE выделяет каждую функцию с этой рекомендацией, независимо от того, использую я возвращаемое значение или нет. Я дважды проверил, возвращаемое значение присваивается переменным и обрабатывается вызывающей стороной.
Я не использую CLion (ничего не имею против; я люблю вещи JetBrains), но я подозреваю, что вы можете отключить его [[nodiscard]]
функцию предложений.
@Элджей Да, я могу. Но мне лучше сначала понять, о чем идет речь, прежде чем идти дальше и игнорировать это. Вероятно, у разработчиков JetBrains изначально была причина активировать его. :)
Некоторые функции вызываются из-за их побочных эффектов. Их возвращаемое значение информативно, но не важно. Другие функции вызываются из-за их возвращаемого значения, отбрасывание возвращаемого значения не имеет смысла, поскольку функция не имеет смысла для вызова. И третья категория функций возвращает код ошибки, а игнорирование кода ошибки обычно является источником ошибок.
@Green绿色 Если вы зайдете в настройки CLion, вы найдете гораздо больше предупреждений, которые отключены, а некоторые включены по умолчанию. Многие предупреждения и предложения активируются только в некоторых ситуациях (например, при разработке встраиваемых систем).
Идея состоит в том, что если имеет смысл вызывать вашу функцию только тогда, когда вы также принимаете ее возвращаемое значение, вызов ее без получения возвращаемого значения является ошибкой программирования. Аннотация [[nodiscard]]
помогает программистам, взаимодействующим с вашим кодом, избежать этой ошибки.
В вашем конкретном примере ваша функция вычисляет результат без побочных эффектов, поэтому статический анализатор кода понимает, что это хорошо подходит для [[nodiscard]]
. Например:
Test a;
auto x = a.f(1, 2); // ok
std::cout << "For 1, 2 we get " << a.f(1,2) << std::endl; // also ok
a.f(1, 2); // warning here
Здесь компилятору рекомендуется в последней строке предупреждать о вызове функции без дальнейшей обработки результата.
Примером хорошего использования [[discard]]
могут быть методы объекта, которые манипулируют объектом, но предоставляют результат в виде копии. Пример:
DateTime yesterday = DateTime.now().substractOneDay();
std::cout << "Yesterday was " << yesterday.nameOfDay() << std::endl;
против:
DateTime yesterday = DateTime.now();
yesterday.substractOneDay();
std::cout << "Yesterday was " << yesterday.nameOfDay() << std::endl;
[[nodiscard]]
сообщит программисту второго примера, что он использует его неправильно.
Обратите внимание, что вы можете отключить предупреждение, приведя к void
, если вы когда-либо хотели намеренно отказаться от значения: void(a.f(1, 2))
Некоторые стандартные библиотечные функции также имеют [[nodiscard]]
, например std::vector::empty()
, чтобы разработчики не путали «что-то пустое» и «очистить что-то».
Хороший ответ! std::async кажется еще одним хорошим примером. Отказ от результата приведет к мгновенному ожиданию завершения асинхронного выполнения iirc.
[[nodiscard]]
полезен, когда у вас есть методы класса, которые что-то возвращают, и вы хотите подчеркнуть, что метод не работает на месте, а вместо этого что-то возвращает.
Например, если у вас есть связанный список, возможный метод reverse
может быть помечен [[nodiscard]]
, чтобы сигнализировать о том, что метод возвращает новый список.
Это помогает избежать такого кода:
List l{1, 2, 3, 4};
l.reverse();
std::cout << l;
>> 1, 2, 3, 4 //Why is this not working?
Мне кажется, что наиболее полезно использовать [[nodiscard]]
, когда у вас есть метод/функция, которая изменяет объект/параметры, но также что-то возвращает (поэтому метод/функция не может быть константой).
List l{1, 2, 3, 4};
l.doubleEveryElementAndSum(); //the sum returned is silently ignored
std::cout << l;
>> 2, 4, 6, 8
Тот факт, что CLion выделяет метод и сообщает, что метод может быть помечен [[nodiscard]
, не имеет ничего общего ни с CLion, ни с компилятором, а с инструментом clang-tidy. Этот инструмент сканирует ваш код и проверяет, что можно улучшить, а затем CLion визуализирует результаты этого инструмента.
Когда вы компилируете свой код, компилятор будет читать аннотацию и выдавать предупреждение всякий раз, когда он находит код, который игнорирует возвращаемое значение функции, отмеченной [[nodiscard]]
.
Я предлагаю не загрязнять ваш код, ставя везде [[nodiscard]]
, как предлагает CLion (clang-tidy). Если вас раздражают предупреждения, вы можете отключить их в настройках clang-tidy.
Что меня смущает, так это то, что если бы он ничего не возвращал, тип возвращаемого значения был бы void
? Эта аннотация действительно кажется мне излишней.
Если вы попытаетесь вызвать метод, помеченный [[nodiscard]]
, и не сохраните возвращаемое значение, компилятор выдаст предупреждение. Без аннотации тот факт, что вы не сохранили значение, не будет генерировать никаких предупреждений.
Нет, в данном случае эта аннотация не нужна.
Предложение добавить его поступило от вашего текстового редактора. Ваш текстовый редактор не является компилятором C++. Только компилятор C++ полностью понимает исходный код C++. Ваш текстовый редактор C++ просто имеет несколько простых правил для добавления предложений, которые, по его мнению, могут быть полезны для улучшения кода. Конкретная причина, по которой он мог показать это предложение, является простым предположением, не зная точно, что делает логика вашего текстового редактора.
какие возвращаемые значения не следует игнорировать.
Ключевое слово там "должны". Игнорирование значений, возвращаемых функциями или методами класса, не является ошибкой. Это само по себе не делает код плохо сформированным. Это правда, что в некоторых случаях игнорирование возвращаемых значений может привести к проблемам. Но для этого должны вступить в силу другие факторы. Например, игнорирование возвращаемого значения из read()
и блаженное предположение, что что-то действительно было прочитано из файла или сокета, почти всегда является ошибкой.
Так, например, int read(int[] buffer, int buffer_size)
обычно модифицирует содержимое входного массива и возвращает количество прочитанных байтов. Если бы я игнорировал n_bytes
и делал только read(my_buffer, 256)
, я бы, вероятно, обрабатывал мусорные данные. Итак, если к подписи [[nodiscard]]
была добавлена аннотация read
, компилятор может помочь своим пользователям обнаружить эту ошибку? Я правильно понял? Это означает, что [[nodiscard]]
— это подсказка моим пользователям о том, что тип возвращаемого значения действительно важно учитывать?
Это предупреждение об использовании
test.f(0, 1);
(тогда какint r = test.f(0, 1);
было бы нормально).