[[noreturn]] обработчик ошибок, вызывающий функцию, которая должна вернуться, все еще получая предупреждение - PullRequest
3 голосов
/ 09 января 2020

В приведенном ниже коде оба обработчика ошибок объявлены как [[noreturn]], я знаю, если функция, которая должна возвращать значение, вызывает функцию, которая выдает и никогда не возвращает, хорошо определенное поведение.

Это было бы неопределенным поведением если управление достигает конца функции f без оператора return, но это не так, потому что return никогда не происходит.

// 'function' must return a value
#pragma warning(default:4716)

class Base
{
public:
    [[noreturn]] virtual void ErrorHandler()
    {
        throw 0;
    }

    int f(int x)
    {
        if (x > 0)
            return x;
        else ErrorHandler();    // C4716
    }
};

class Derived :
    public Base
{
public:
    [[noreturn]] void ErrorHandler() override
    {
        throw 1;
    }
};

int main()
{
    Base b;
    b.f(0);

    Derived d;
    d.f(0);
}

Это все еще UB, если нет, то почему я получаю предупреждение?

В дополнение к вышеупомянутой проблеме я хочу проект, в котором ErrorHandler мог бы обрабатывать исключение и возвращать управление вызывающей стороне, в таком случае, как мне узнать, возвращает переопределенный обработчик или нет?

Например мы могли бы просто удалить [[noreturn]] и предположить, что обработчик может или не может вернуться, тогда как спроектировать функцию f? что возвращать, если аргумент равен нулю, но возвращаемое значение должно быть ненулевым?

Первично я хочу избавиться от предупреждения и убедиться, что поведение правильно определено, а возвращаемое значение должно быть либо положительным, в противном случае функция f не должен продолжаться.

edit

Если мы изменим код как имеющий не виртуальный обработчик ошибок, предупреждение исчезнет.

, так что же делает Первый случай поднять предупреждение? какая разница?

   class Base
    {
    public:
        [[noreturn]] void ErrorHandler()
        {
            throw 0;
        }

        int f(int x)
        {
            if (x > 0)
                return x;
            else ErrorHandler();    // OK
        }
    };

    class Derived :
        public Base
    {
    public:
        [[noreturn]] void ErrorHandler()
        {
            throw 1;
        }
    };

    int main()
    {
        Base b;
        b.f(0);

        Derived d;
        d.f(0);
    }

Ответы [ 3 ]

3 голосов
/ 09 января 2020

Компилятор знает что-то, чего вы не знаете. [[noreturn]] не является частью сигнатуры метода, поэтому переопределенные методы в производных классах могут свободно возвращаться.

// 'function' must return a value
#pragma warning(default:4716)

class Base
{
public:
    [[noreturn]] virtual void ErrorHandler()
    {
        throw 0;
    }

    int f(int x)
    {
        if (x > 0)
            return x;
        else ErrorHandler();    // C4716
    }
};

class Derived :
    public Base
{
public:
    [[noreturn]] void ErrorHandler() final // FINAL is key
    {
        throw 1;
    }

    int f(int x)
    {
        if (x > 0)
            return x;
        else ErrorHandler();    // No C4716 here!
    }
};

class Derived2 :
    public Base
{
public:
    void ErrorHandler() override
    {
      return; // AHA!  ErrorHandler returns!
    }
};


int main()
{
    Base b;
    b.f(0);

    Derived d;
    d.f(0);

    Derived2 d2;
    d2.f(0);
}

здесь я создал дополнительный производный класс и добавил метод f в Derived.

Единственное предупреждение, которое я получаю:

<source>(17) : warning C4715: 'Base::f': not all control paths return a value

, правильно выводит, что Derived::f (копия Base::f) не имеет этой проблемы .

Проблема в Base::f обозначена Derived2 - в ней ErrorHandler была переопределена без атрибута [[noreturn]]. Атрибуты не являются частью сигнатур методов .

Так что virtual ErrorHandler, то есть [[noreturn]], может быть переопределено тем, который возвращает. Это приведет к тому, что f будет демонстрировать неопределенное поведение.

Когда в Derived я отмечаю ErrorHandler как final, f там не может демонстрировать неопределенное поведение ( поскольку нет способа переопределить ErrorHandler с перегрузкой не [[noreturn]]). Таким образом, предупреждение не генерируется.


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

Самый чистый способ this:

class Base
{
public:
  [[noreturn]] void DoErrorHandling() {
    ErrorHandler();
    throw 0; // or std::terminate
  }
private:
  [[noreturn]] virtual void ErrorHandler() { throw 0; }
public:
  int f(int x) {
    if (x > 0)
      return x;
    DoErrorHandling();
  }
};

Теперь мы обертываем ErrorHandler не виртуальным DoErrorHandling методом, помеченным [[noreturn]], который, если виртуальный ErrorHandler, который он вызывает, не выбрасывает, выбрасывает.

3 голосов
/ 09 января 2020

[[noreturn]] virtual void ErrorHandler() только говорит, что эта функция не возвращает.

Я не заставляю наследуемые классы «наследовать» атрибут.

Если вы хотите вызвать исключение, вы можете вернуть std::exception_ptr вместо:

class Base
{
public:
    [[noreturn]] virtual std::exception_ptr ErrorHandler()
    {
        std::make_exception_ptr(0);
    }

    int f(int x)
    {
        if (x > 0) {
            return x;
        } else {
            auto eptr = ErrorHandler();
            if (eptr) {
                rethrow_exception(eptr);
            }
            throw "nullptr eptr";
        }
    }
};

class Derived :
    public Base
{
public:
    [[noreturn]] std::exception_ptr ErrorHandler() override
    {
        std::make_exception_ptr(1);
        // or even
        // throw 1;
    }
};
0 голосов
/ 09 января 2020

Как говорится в сообщении, компилятор не может определить, достигнуто ли управление до конца, и он что-то возвращает, однако с небольшими изменениями в операторе if и быстрым сбоем.


class Base
{
public:
    [[noreturn]] virtual void ErrorHandler()
    {
        throw 0;
    }

    int f(int x)
    {
        if (x <= 0)
            ErrorHandler();
        return x;
    }
};

class Derived :
    public Base
{
public:
    [[noreturn]] void ErrorHandler() override
    {
        throw 1;
    }
};

int main()
{
    Base b;
    b.f(0);

    Derived d;
    d.f(0);
}

Предупреждение отсутствует и код компилируется , компилятор видит, что есть оператор return a end, поэтому функция вернет некоторое значение int

...