[[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]]
Функция приводит к неопределенному поведению, и я, конечно, не хочу, чтобы переопределение также наследовало атрибут.
Вопрос: Переопределением [[noreturn] virtual void B::f()
я наследую [[noreturn]]
атрибут?
Я просмотрел стандарт C ++ 14, и у меня возникли проблемы с определением, являются ли атрибуты наследуемыми.
На практике ни г ++, лязг ни 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» для всех трех компиляторов.
Я ознакомился со стандартом, и нет никаких признаков того, что либо [[noreturn]]
в частности, или атрибуты в более общем смысле, «наследуются» переопределяющими функциями.
Трудно доказать отрицание, и стандарт фактически не объявляет это в любом случае, но, так как A::f()
а также B::f()
все еще являются различными функциями, и единственное описанное поведение определено в терминах функций, я думаю, что вы можете отметить A::f()
как [[noreturn]]
,
При этом я не могу себе представить, какую полезную оптимизацию мог бы выполнить компилятор, учитывая динамическую диспетчеризацию.
Подумайте, что вы на самом деле говорите:
class B
{
public:
[[noreturn]] virtual void f() { throw std::runtime_error(""); }
};
Конечно, человеческий читатель и, возможно, компилятор интерпретируют
это как «контракт», т.е.
«f()
не вернусь, я обещаю
Это должно затем применяться к переопределениям f()
или вы нарушаете контракт.
Стандарт может быть недостаточно конкретизирован по этому вопросу, но даже если он работает, я бы рекомендовал против него, основываясь на удобочитаемости.