Можно ли объявить функцию С++, чтобы нельзя было игнорировать возвращаемое значение?
Я пытаюсь определить, можно ли объявить функцию С++ таким образом, чтобы возвращаемое значение не могло быть проигнорировано (идеально определено во время компиляции). Я попытался объявить класс с private
(или в С++ 11, delete
d) operator void()
, чтобы попытаться поймать неявное преобразование в void, когда возвращаемое значение не используется.
Вот пример программы:
class Unignorable {
operator void();
};
Unignorable foo()
{
return Unignorable();
}
int main()
{
foo();
return 0;
}
К сожалению, мой компилятор (clang-703.0.31) говорит:
test.cpp:2:5: warning: conversion function converting 'Unignorable' to 'void' will never be used
operator void();
^
и не вызывает никаких ошибок или предупреждений при вызове foo()
. Так что это не сработает. Есть ли другой способ сделать это? Ответы, специфичные для С++ 11 или С++ 14 или более поздних версий, будут прекрасными.
Ответы
Ответ 1
Подводя итог другим ответам и комментариям, в основном у вас есть 3 варианта:
- Получить С++ 17, чтобы иметь возможность использовать
[[nodiscard]]
- В g++ (также clang++) используйте расширения компилятора, такие как
__wur
(определенные
как __attribute__ ((__warn_unused_result__))
) или более переносимый (только С++ 11 и только) [[gnu::warn_unused_result]]
.
- Используйте проверки времени выполнения, чтобы уловить проблему во время модульного тестирования.
Если все эти 3 невозможны, то есть еще один способ, который выглядит как "Отрицательный компиляция" . Определите Unignorable
как показано ниже:
struct Unignorable {
Unignorable () = default;
#ifdef NEGATIVE_COMPILE
Unignorable (const Unignorable&) = delete; // C++11
Unignorable& operator= (const Unignorable&) = delete;
//private: Unignorable (const Unignorable&); public: // C++03
//private: Unignorable& operator= (const Unignorable&); public: // C++03
/* similar thing for move-constructor if needed */
#endif
};
Теперь скомпилируйте с помощью -DNEGATIVE_COMPILE
или эквивалент в других компиляторах, таких как MSVC. Он будет давать ошибки везде, где результат не игнорируется:
auto x = foo(); // error
Однако он не будет давать никаких ошибок, где бы ни был проигнорирован результат:
foo(); // no error
Используя любой современный браузер кода (например, eclipse-cdt), вы можете найти все вхождения foo()
и исправить те места, которые не дали ошибки. В новой компиляции просто удалите предопределенный макрос для "NEGATIVE_COMPILE".
Это может быть лучше, чем просто найти foo()
и проверить его возврат, потому что может быть много функций, таких как foo()
, где вы можете не игнорировать возвращаемое значение.
Это немного утомительно, но будет работать для всех версий С++ со всеми компиляторами.
Ответ 2
До начала С++ 17 этот подход пришел на ум:
#include <stdexcept>
#include <exception>
#include <boost/optional.hpp>
// proxy object which complains if it still owns the return
// value when destroyed
template<class T>
struct angry
{
angry(T t) : value_(std::move(t)) {}
angry(angry&&) = default;
angry(angry const&) = default;
angry& operator=(angry&&) = default;
angry& operator=(angry const&) = default;
~angry() noexcept(false)
{
if (value_) throw std::logic_error("not used");
}
T get() && {
T result = std::move(value_).value();
value_.reset();
return result;
}
boost::optional<T> value_;
};
// a function which generates an angry int
angry<int> foo()
{
return 10;
}
int main()
{
// obtain an int
auto a = foo().get();
// this will throw
foo();
}
Сводка: вместо того, чтобы возвращать Т, функция возвращает angry<T>
, которая будет наказывать вызывающего, бросая logic_error
, если значение не извлекается до уничтожения.
Это временное решение, которое является ограничением, но по крайней мере должно быть обнаружено на ранних этапах модульных тестов.
Пользователь canny может, конечно, подорвать его:
foo().get(); // won't throw
Ответ 3
См. __ attribute__ ((warn_unused_result)).
int foo() __attribute__ ((warn_unused_result));
int foo(){return 123;}
int main()
{
foo(); //compiler warning
auto i = foo(); //valid
}
Затем заставьте предупреждение быть ошибкой:
clang++ -std=c++1z -Werror="unused-result"