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

Переопределение виртуальной функции [[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, и у меня возникли проблемы с определением того, наследуются ли атрибуты.

4b9b3361

Ответ 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(), иначе вы нарушаете контракт.

Стандарт может быть указан ниже, но даже если он работает, я бы рекомендовал против него на основе удобочитаемости.