3

I'm currently trying to get my head around noexcept (like almost everyone I avoided the old "runtime exception specification"). Whilst I think I get the basic idea of noexcept, I'm not sure what happens in a situation like:

class sample {
public:
  sample() noexcept { }//this doesn't throw
  sample(const sample & s) noexcept { }
  sample(sample && s) noexcept { }
  sample & operator=(const sample & s) noexcept {...}
  sample & operator=(sample && s) noexcept { ... }
  ~sample() noexcept() { }//this should never ever throw
  sample operator-() const { return *this * -1; }//assuming that there is a operator*…
  sample & operator*=(const sample & s) noexcept { ... }
};

sample operator*(sample s1, const sample & s2) { return s1 *= s2; }//same problem as with operator-…

Is it safe to declar sample::operator- as noexcept, or not? (considering that it's calling a constructor on return)

EDIT: I updated the code section as it seems that the central part of the question was not clear…

MFH
  • 1,664
  • 3
  • 18
  • 38
  • 1
    @111111: If you can guarantee that the operation is `noexcept`, there is no problem with declaring it. In some cases you might even want to do it to be able to implement other functions with the *strong exception guarantee*, consider for example `swap`. – David Rodríguez - dribeas Mar 17 '12 at 20:35
  • 1
    @MFH, without the declaration of the `operator*` that is being called, it is impossible to know whether it is safe or not. – David Rodríguez - dribeas Mar 17 '12 at 20:38
  • @111111: So they added a new version of exception specifications just be ignored again? I find it hard to believe that they made the same mistake again… – MFH Mar 17 '12 at 20:38
  • @MFH: There are C++ programmers on this site who love nothing better than to shove their prejudices and coding habits down the throat of others. It's best to ignore them. – Nicol Bolas Mar 17 '12 at 20:40
  • @MFH: they *rewrote* the exception specification for the only sensible use that there is: promising that you will not throw. And in the process they changed the syntax to accept a boolean parameter and thus allow reasoning about the exception specifications based on other exception specifications... – David Rodríguez - dribeas Mar 17 '12 at 20:40
  • @MFH care to link some information about the rewriting? I was aware they deprecated specification was unaware they changed the mean of noexcept – 111111 Mar 17 '12 at 20:46
  • ... on the other hand, I have to agree at least a bit in that I would not start throwing `noexcept` specifications to all of the operations, but mainly those that are important for *exception guarantees*. I don't know what `sample` does, but if you later change it and any of the functions need to throw you are heading for quite a bit of painful refactoring. Consider if `operator+=` needed to throw, you suddenly need to refactor all of the other operators that depend on it, and all of the user code that might depend on your promise of not throwing from any of the operators – David Rodríguez - dribeas Mar 17 '12 at 20:47
  • @111111 They have changed `throw( )` to `noexcept( )` that's the main rewrite. The committee decided that the only sensible use of throw specifications is `throw()` and came with a new syntax that focuses on that. The extra argument is to be able to use logic operations in the declaration (*does not throw if this and that other operations promise not to throw*) But the same comment I added before also applied for C++03, the `throw()` (no throw) exception specification makes sense in some cases to offer exception guarantees. – David Rodríguez - dribeas Mar 17 '12 at 21:06
  • @DavidRodríguez-dribeas ok thanks, and is there actually any advantages to specifying no throw, all I can tell is that you are more likely to get term called if it does throw when it shouldn't. It is not like throw(true) forces you to surround it with try catch? – 111111 Mar 17 '12 at 21:10

2 Answers2

2

After the edit: Your implementation of operator- is guaranteed not to throw any exception (well, at least if you mark operator* as noexcept, that is), and it is thus safe to mark it as noexcept. I don't really understand your concern, though so I might be missing the reason for the question. All of the operations, including the potential copy or move construction are explicitly marked noexcept... where is the issue?


Unless you explicitly mark it as noexcept it will not have that qualification. Now, depending on the implementation of operator* and the copy-constructor it might actually never throw, but that does not make it noexcept.

As of the copy-constructor, if you don't define it, the implicitly declared copy constructor will be noexcept or not depending on whether all the members of your type are noexcept (again, not only that they don't throw, but that they have that qualification)

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • @MFH: What is worrying you? Why would it not be safe? Copy construction, move construction both to create the parameter and to return the value are `nothrow`, and the only other operation in the class is also `nothrow`. What could possibly `throw`? -- A different thing is whether you want to be explicitly marking *all* the functions as `nothrow`. – David Rodríguez - dribeas Mar 17 '12 at 20:57
  • If you had not declared your own copy/move constructors, then you would have to refer back to the first part of the answer: the implicitly declared copy/move constructor will be `nothrow` or not based on the copy/move specifications of the members. – David Rodríguez - dribeas Mar 17 '12 at 20:58
  • i think what worried me the most were the implicit copies that may lead to running out of memory, which in turn may lead to an exception…No rethinking operator*: that won't happen as the copy was made on the call. so operator* is noexcept (as *= is noexcept). Heading to operator-: I copy this (noexcept) and move the result of the * -1 out… (so unless I run out of memory (or break the "promise") this can't throw either… – MFH Mar 17 '12 at 21:08
  • @MFH: The copies will be *stack* allocated, unless your object manages memory, in which case your copy constructor should not be marked `nothrow` as you cannot fulfill that promise. Even in that case, if you have provided a *move constructor* that offers the guarantee the compiler must prefer the move construction to the copy construction and it would still be fine. – David Rodríguez - dribeas Mar 17 '12 at 21:12
  • Maybe I've been programming in a restricted environment too long lately… (fearing a stack overflow due to object allocation) – MFH Mar 17 '12 at 21:14
  • @MFH: You should read your compiler documentation, but in most cases it is the caller the one that allocates space for the returned object. If you were to trigger a stackoverflow it would not be the return statement. Also note that stackoverflow does not *throw* an exception, but rather cause undefined behavior, which in turn means that you are not breaking the `nothrow` contract. – David Rodríguez - dribeas Mar 17 '12 at 23:07
1

Your question is, "is it safe?". I would rather ask, "does it even make sense to declare some functions as noexcept only because they do not throw exceptions?".

Strictly speaking, there are two things that noexcept offers:

  1. Anyone can check at compile-time if your function is declared as noexcept. (This is, in practice useful only in one std function: move_if_noexcept)
  2. You get a run-time guarantee that if your function nonetheless tries to throw an exception, it does not get out of the function (because std::terminate is called instead).

More interestingly, to list what noexcept does not offer:

  1. Automatic checking at compile-time if the function really doesn't throw
  2. Even more importantly, it does not offer any "exception safety guarantee".

I believe that the second item is often overlooked when learning the noexcept. If you want to provide a no-fail guarantee (note that this is different than no-throw guarantee, because the function may fail, but not throw), you can simply implement it in your function and throw nothing, and document it somewhere. You do not need to mark your function as noexcept. It will provide no-fail exception safety anyway.

About the only reasonable place for using noexcept is when providing move constructor or move assignment in your type, and only if you feel you need to define them yourself rather than relying on compiler-generated versions. This is useful, because some STL container operations will work faster, because they use function std::move_if_noexcept in their implementations.

I also recommend this article on details of noexcept functionality.

Andrzej
  • 5,027
  • 27
  • 36