21

Is it safe to declare the following function noexcept even though v.at(idx) could theoretically throw a out_of_range exception, but practically not due to the bounds check?

int get_value_or_default(const std::vector<int>& v, size_t idx) noexcept {
    if (idx >= v.size()) {
        return -1;
    }
    return v.at(idx);
}
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
j00hi
  • 5,420
  • 3
  • 45
  • 82
  • Plus one, but this isn't necessarily safe. What happens if another thread modifies `v`? – Bathsheba Aug 04 '15 at 12:29
  • 9
    @Bathsheba, then the code has undefined behaviour already because it calls `v.size()` and `v.at(idx)`without synchronisation, so it's pointless to worry about such things. Either that doesn't happen, or the program has bigger problems. – Jonathan Wakely Aug 04 '15 at 12:37
  • 2
    This code is not thread safe. However, the question seems to aim for single thread case only. – Matthias J. Sax Aug 04 '15 at 12:37
  • 9
    You may as well return `v[idx]` - there's no point checking the range twice – GuyRT Aug 04 '15 at 12:38
  • 2
    @GuyRT It might have been for the sake of the example – KABoissonneault Aug 04 '15 at 13:07
  • @GuyRT: Except an out-of-bounds access through `int& std::vector::operator[]` will not return `-1`. Your suggestion doesn't work for this function. – Lightness Races in Orbit Aug 04 '15 at 13:26
  • @LightnessRacesinOrbit Why? if you check if the size is out of bounds why would you need to use `at()` which is going to check again? an out of bounds access through `at()` wont return `-1` either – NathanOliver Aug 04 '15 at 13:38
  • @NathanOliver: Oh, you mean _after_ the conditional. Gotcha. I read the suggestion as replacing the entire function with a single `return` statement. In hindsight, I'm not sure why. – Lightness Races in Orbit Aug 04 '15 at 13:38
  • @GuyRT: But then the example would no longer theoretically generate an exception and thus would be inappropriate... – Matthieu M. Aug 04 '15 at 14:50
  • @MatthieuM: Point taken - It is a good example for the question. My comment would only be helpful if the question was prompted by the example (rather than vice versa), which I suspect isn't the case. – GuyRT Aug 04 '15 at 16:10

5 Answers5

15

What is you definition of "safe"? If you throw an exception in a function marked asnoexcept or noexcept(true) then your program will be terminated (standard 15.4.9)

Whenever an exception is thrown and the search for a handler (15.3) encounters the outermost block of a function with an exception-specification that does not allow the exception, then,

  • if the exception-specification is a dynamic-exception-specification, the function std::unexpected() is called (15.5.2),
  • otherwise, the function std::terminate() is called (15.5.1).

As long as that is acceptable then you are fine. If you can not tolerate the program terminating then it is not safe.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 5
    The point is that no exception will be thrown, ever, so even if the program cannot tolerate termination then it is still safe because it won't call std::terminate(). – Jonathan Wakely Aug 04 '15 at 12:40
  • @JonathanWakely Can you guarantee an exception will never be thrown from that function? since the function is not thread safe the vector could be modified by another thread and the size changes between the bounds check and the retrieval. – NathanOliver Aug 04 '15 at 12:50
  • 5
    @NathanOliver: As Jonathan pointed out elsewhere, if these accesses are unsynchronised then you have far bigger problems that overshadow anything you could possibly do inside this function (besides introducing synchronisation). Such efforts would literally be pointless. – Lightness Races in Orbit Aug 04 '15 at 13:28
9

It is safe. Declaring a function noexcept would result in immediate program termination (by a call to terminate()) if an exception occurs. As long as no exception is thrown everything is fine.

Matthias J. Sax
  • 59,682
  • 7
  • 117
  • 137
4

We don't have the full scenario to determine exactly whether that function will be able to throw.


Your assumptions are though correct: since you're doing what std::vector::at will anyway do, it is unlikely to be harmful in the given context. However, generally talking, such function may be subject of invalidating factors and thus potentially able to throw an exception. These could be threads, processes or signals that could interleave the execution to code that might modify std::vector, which can't handle that safely and neither can std::vector::at.

If you want to consider those possibilities as well, you'll need to have something like

int get_value_or_default(const std::vector<int>& v, size_t idx) noexcept       
{
    try { return v.at(idx); }
    catch (std::out_of_range const& e) { return -1; }
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
edmz
  • 8,220
  • 2
  • 26
  • 45
  • 4
    As Jonathan pointed out elsewhere, if these accesses are unsynchronised then you have far bigger problems that overshadow anything you could possibly do inside this function (besides introducing synchronisation). Such efforts would literally be pointless. – Lightness Races in Orbit Aug 04 '15 at 13:28
  • 1
    @LightnessRacesinOrbit That is true, but strictly depends on the overall and actual code. There could be no asynchronous accesses which would vanish such worries. I tried to focus on the `noexcept`ness of the code because that's the main point of the question, what could asynchronous effects do is definitely beyond. – edmz Aug 04 '15 at 13:55
  • 1
    Right, and given that, there is no problem with the `noexcept`. – Lightness Races in Orbit Aug 04 '15 at 14:34
2

even though v.at(idx) could theoretically throw a out_of_range exception, but practically not due to the bounds check?

Theoretically, it could not.

If you're thinking theoretically about that function, then the bounds check is part of what you must consider about the theory of that function as a whole, and so the only theoretical way it could throw is if there was a condition in which idx >= v.size() wasn't true and yet v.at(idx) threw.

So in your statement that it "could theoretically throw" is wrong.

(In practice it could throw, if you had a bug in the implementation of at() in use, but then you've got bigger problems and blowing up as noexcept could lead you to do is probably for the better).

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
0

It is possible that someone in big-O organisation has done smart and derived his class from std::vector<int> and then overloaded both size() and at() to throw to his liking . Then somewhere in the code your function is called , but to everyone's surprise the program terminates instead of the exception being caught higher up.

Far fetched indeed, but this not a question just about std::vector<int> ... , but about the programming practice behind.

Unfortunately, you are assuming things about your input that cannot be guaranteed, and that is why it is unsafe; at least in large code domains.

g24l
  • 3,055
  • 15
  • 28