Переопределение виртуальной функции [[noreturn]]
Атрибут [[noreturn]]
может применяться к функциям, которые не предназначены для возврата. Например:
[[noreturn]] void will_throw() { throw std::runtime_error("bad, bad, bad ...."); }
Но я столкнулся со следующей ситуацией (нет, я не проектировал это):
class B {
public:
virtual void f() { throw std::runtime_error(""); }
};
class D : public B {
void f() override { std::cout << "Hi" << std::endl; }
};
Мне бы хотелось разместить атрибут [[noreturn]]
в объявлении B::f()
. Но я не понимаю, что происходит с переопределением в производном классе. Успешное возвращение из функции [[noreturn]]
приводит к поведению undefined, и я, конечно, не хочу, чтобы это переопределение также наследовало атрибут.
Вопрос: Переопределяя [[noreturn] virtual void B::f()
, наследую ли атрибут [[noreturn]]
?
Я просмотрел стандарт С++ 14, и у меня возникли проблемы с определением того, наследуются ли атрибуты.
Ответы
Ответ 1
На практике ни g++, clang и MSVC рассматривают атрибут [[noreturn]]
как унаследованный
#include <iostream>
struct B {
public:
[[noreturn]] virtual void f() { std::cout << "B\n"; throw 0; }
};
struct D : public B {
void f() override { std::cout << "D\n"; }
};
int main()
{
try { B{}.f(); } catch(...) {}
D{}.f();
B* d = new D{};
d->f();
}
который печатает "B", "D" и "D" для всех трех компиляторов.
Ответ 2
Я прошел через стандарт, и нет никаких указаний на то, что либо [[noreturn]]
в частности, либо атрибуты в более общем плане "наследуются" посредством переопределения функций.
Трудно доказать отрицание, и стандарт фактически не объявляет об этом в любом случае, но поскольку A::f()
и B::f()
все еще являются различными функциями, и только описанное поведение определяется в терминах функций, я думаю, вы безопасно отмечать A::f()
как [[noreturn]]
.
При этом я не могу представить, какую полезную оптимизацию мог бы выполнить компилятор, учитывая динамическую отправку.
Ответ 3
Подумайте, что вы на самом деле говорите:
class B
{
public:
[[noreturn]] virtual void f() { throw std::runtime_error(""); }
};
Конечно, человеческий читатель и, возможно, компилятор интерпретируют
это как "контракт", т.е. "f()
не вернется, я обещаю"
Это должно также применяться к переопределению f()
, иначе вы нарушаете контракт.
Стандарт может быть указан ниже, но даже если он работает, я бы рекомендовал против него на основе удобочитаемости.