2015-09-18 5 views
18

Атрибут [[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]] приводит к неопределенному поведению, и я, конечно, не хочу этого, если переопределение также наследует атрибут.

Вопрос: Переопределелив [[noreturn] virtual void B::f(), я наследовать атрибут [[noreturn]]?

Я просмотрел стандарт C++ 14, и у меня возникли проблемы с определением того, наследуются ли атрибуты.

+1

Я не знаю, просто догадываясь, но в тот момент, когда вы вызываете B-> f(), компилятор, скорее всего, не будет знать, что такое динамический тип B, поэтому я думаю, что компилятор будет оптимизирован с помощью noreturn. Я не знаю, почему этот дизайн, если это потому, что у B нет реализации для этого, и этот бросок является признаком «не называйте это, только на производных классах», а затем подпишите его с помощью noreturn, не имеет смысла мне. (ну, сам дизайн также не имеет большого смысла.: D).Если вы используете статический тип D, я ожидаю, что компилятор не будет оптимизирован для noreturn. Опять же: я просто догадываюсь. – Melkon

+0

's/не предназначен для возврата значения/не предназначен для возврата вообще /' –

+0

@LightnessRacesinOrbit Спасибо. Отредактированный оригинал. после. – KyleKnoepfel

ответ

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" для всех трех составителей.

+0

Поскольку возврат из функции «noreturn» - это UB, каково ожидаемое поведение? – dyp

+1

@dyp, возвращающийся из 'BB {l.f()', дает предупреждение о clang, поэтому я ожидал предупреждения для 'd.f()', если noreturn был транзитивным (evern, если UB является ndr) – TemplateRex

8

Я прошел через стандарт, и нет никаких указаний на то, что либо [[noreturn]] конкретно, либо атрибуты в целом, «наследуются» посредством переопределения функций.

Это трудно доказать обратное, и стандарт фактически не декларирует это так или иначе, но, так как A::f() и B::f() еще различные функции и только поведение, описанное определяется в терминах функций, я думаю, что вы в безопасности для отметки A::f() как [[noreturn]].

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

+2

** + 1 **: Атрибуты не наследуются с момента их применения ([стандартная формулировка] * "appertains" *) субъекту, в котором они написаны. В примере OPs '[[noreturn]]' применяется к 'void B :: f()' - 'void D :: f()' - совершенно другой объект. –

3

Рассмотрим то, что вы на самом деле говорят:

class B 
{ 
public: 
    [[noreturn]] virtual void f() { throw std::runtime_error(""); } 
}; 

Конечно, человеческий читатель, и, возможно, компилятор, будет интерпретировать это как «контракт», т.е.
«f() не возвратит, я обещаю»

Это должно также применяться к переопределению f(), иначе вы нарушаете договор.

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

+0

Для меня не очевидно, что '[[noreturn]' указывает контракт больше, чем спецификатор доступа базового класса указывает контракт. А именно, общедоступный/защищенный/закрытый доступ к переопределению функции в производном классе не зависит от базового класса. Тем не менее, для меня последнее значение связано с вашим последним заявлением. Поскольку стандарт не указывает его, то он подпадает под неопределенное поведение, и поэтому я не должен зависеть от него. – KyleKnoepfel

+1

@KyleKnoepfel Контракт между вызывающим и вызываемым, а не между базовым и производным. Спецификатор доступа может считаться частью этого контракта. 'public: void f()' обещает, что 'b-> f()' всегда будет доступным, и это обещание будет выполняться, даже если 'f' переопределяется частной функцией в' D'. Лучшим аргументом было бы то, что '[[noreturn]]' больше не является частью контракта, чем '= 0' чистой виртуальной функции. – Oktalist

Смежные вопросы