Подтвердить что ты не робот

Можно ли объявить функцию С++, чтобы нельзя было игнорировать возвращаемое значение?

Я пытаюсь определить, можно ли объявить функцию С++ таким образом, чтобы возвращаемое значение не могло быть проигнорировано (идеально определено во время компиляции). Я попытался объявить класс с 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 или более поздних версий, будут прекрасными.

4b9b3361

Ответ 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"