Ali-Baba
Ali-Baba

Reputation: 198

Is there a difference between "throw MyException()" and "throw (MyException())"?

I want to know if writing exceptions inbox and out box is changing the behavior or not of a particular program, for example throw MyException(); and throw (MyException());

My Code:

#include <iostream>
#include <exception>
using namespace std;

class MyException: public exception {
public:
      virtual const char* what() const throw()
      {
         return "Something bad happened";
      }
};
class Test
{
public:
   void goWrong()
   {
      throw (MyException());
   }
};

int main()
{
   Test test;
   try
   {
      test.goWrong();
   }
   catch (MyException &err)
   {
      cout << "The Exception is Executed: " << err.what() << '\n';
   }
   cout << "Still Running" << '\n';
   return 0;
}

Upvotes: 7

Views: 142

Answers (2)

Asteroids With Wings
Asteroids With Wings

Reputation: 17454

The exception object is copy-initialised (except.throw/3), so it doesn't really matter which one you use; the result is the same.

Copy-initialising would ignore a reference qualifier even if you got one out of this.

We can prove this with some trace output:

#include <cstdio>
using std::printf;

struct T
{
    T() { printf("T()\n"); }
    ~T() { printf("~T()\n"); }
    T(const T&) { printf("T(const T&)\n"); }
    T(T&&) { printf("T(T&&)\n"); }
    T& operator=(const T&) { printf("T& operator=(const T&)\n"); return *this; }
    T& operator=(const T&&) { printf("T& operator=(T&&)\n"); return *this; }
};

int main()
{
    try
    {
        throw T();
    }
    catch (const T&) {}
}

Even if you switch from throw T() to throw (T()) the semantics (and output) are exactly the same:

T()
T(T&&)
~T()
~T()

That is, a temporary T() is constructed, then moved into the real exception object (which exists in some magical "safe space"), and ultimately both are destructed.

Note that, to see this proof, you do have to go back to C++14 (as C++17 doesn't materialise that temporary until the real exception object is needed, per so-called "mandatory elision") and turn off pre-C++17 optional elision (e.g. -fno-elide-constructors in GCC).

If, as some others have claimed, there could be such a thing as "throwing a reference" (which became dangling), you'd only ever see one construction of T. In truth, there is no such thing as expressions of reference type, despite the best efforts of decltype and auto to pretend to you that there are.

In both cases, the expression we give to throw is an rvalue MyException.

The reason we have to be careful with return when using a deduced return type (via decltype(auto)), is that deduction considers value category. If you have a local variable int x, then in return x the expression x is xvalue int, so your deduced type will be int and everything is fine. If you write return (x) instead then the expression is lvalue int, which results in a deduced type of int& and suddenly you have problems. Notice that neither expression has reference type (a thing which effectively does not exist). But exactly none of this is relevant in your throw scenario anyway, not least of all because your argument is a temporary in the first place.

Upvotes: 6

Anonymous1847
Anonymous1847

Reputation: 2598

I believe it would work like a return statement in C++14 and later, in that parentheses indicate the type to be thrown to be a reference, not the exception instance itself. The reference would be stored and copied and treated like an exception, but the temporary object the reference points to would probably be destroyed in stack unwinding, which may cause problems. It may not cause immediate issues if your exception object is of POD type, since destruction for such an object created on the stack is trivial, but it's still undefined behavior.

Upvotes: -5

Related Questions