1

I have a use case of unions, but as many programmers I quite find it ugly to use unions. So I tried using exception handling not in the way it is meant to be. I understand that this will introduce some loss of time due to handling of exceptions. My question is : is there a clean way to do that ?

here is the code, with union, and without

//g++  7.4.0
#include <iostream>
using namespace std;
class C{ int i ; public : C(int i):i(i){cout << "C(" << i << ")\n";} void display() const { cout << "#C(" << i << ")\n"; } };
class D{ int i ; public : D(int i):i(i){cout << "D(" << i << ")\n";} void display() const { cout << "#D(" << i << ")\n"; } };
class E{ int i ; public : E(int i):i(i){cout << "E(" << i << ")\n";} void display() const { cout << "#E(" << i << ")\n"; } };

struct CDE { enum {C_t,D_t,E_t} kind; union { C c; D d; E e; }; CDE(){} };
CDE f(int i){ 
    CDE res;
    if( i==1 ) { res.kind = CDE::C_t; res.c = C(1); return res; }
    if( i==2 ) { res.kind = CDE::D_t; res.d = D(2); return res; }
    res.kind = CDE::E_t; res.e = E(i); return res;
}

void g(int i){
    if( i==1 ) throw C(1);
    if( i==2 ) throw D(2);
    throw E(i);
}

int main(){
    cout << "/** trace\n\nusing union\n";{
        CDE res = f(1);
        if (res.kind==CDE::C_t){ res.c.display(); }
        if (res.kind==CDE::D_t){ res.d.display(); }
        if (res.kind==CDE::E_t){ res.e.display(); }
    }cout << "\nusing exceptions\n";{
        try{
            g(1);
        }
        catch(const C& c){ c.display(); }
        catch(const D& d){ d.display(); }
        catch(const E& e){ e.display(); }
    }cout << "\nstop\n*/\n";
}

and here it the (obvious) trace I get

/** trace

using union
C(1)
#C(1)

using exceptions
C(1)
#C(1)

stop
*/
timrau
  • 22,578
  • 4
  • 51
  • 64
chetzacoalt
  • 145
  • 10
  • 4
    [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant) is kind of a type safe union. – Lukas-T Jul 24 '20 at 08:29
  • Please read this quote, The answers tell you the use of union. https://stackoverflow.com/questions/62975043/how-union-is-helpful-over-a-variable/62975108#62975108. – Kungfu Frog Jul 24 '20 at 08:30
  • @churill as I understand std::variant manages some of the aspects of union+struct, but in the end, I will need to create a new type (variant) with a fixed set of alternatives, and check each. on the other hand, using exceptions seems to me like some kind of duck-typing approach. (I don't know if I manage to express my idea clearly, I'm not native english speaking) – chetzacoalt Jul 24 '20 at 08:42
  • 1
    Yes, that's the point of type safety, you get a defined set of types a `std::variant` can contain and can check what type it actually contains at runtime. Just like with an `union`, just safer. Using exceptions can kill your performance and has a different semantic. Imagine such a piece of code could throw an actual exception. You would have a mess of `catch`-blocks, where some will handle a "return value" and other will handle an actual exception. It would be quite hard to understand for others and probably for your future self ;) – Lukas-T Jul 24 '20 at 08:46
  • Alternatively, if it's just about calling some functions that are common to a set of types inheritance, virtual functions and smart pointers might be the way to got. If I find some time I'll try to conclude this in an answer. – Lukas-T Jul 24 '20 at 08:53
  • @churill yes thanks, I'd love that ! – chetzacoalt Jul 24 '20 at 08:57

2 Answers2

3

I'd strongly suggest against using exception for this, it's error-prone, and not what they are meant for. One of the issue would be extensibility. Using exception, say you add one type, you have to be sure you've add it to your try-catch statement. Moreover, it's a bad habit, because it break the usual code flow. (say you add something after g(), it will never be called. Another issue is that if there are actual exception, you would have logic mixed with error handling in the same catch block, which would then become harder to read. Or you might have code throwing an exception while an exception was already thrown, which would stop the execution altogether.

If you want to use union, you could use std::variant, or use a switch statement on your enum (which is better than using ifs one after the other.)

However, in C++, and most Object Oriented language, there is a better way to achieve what you want here, using inheritance:

class C{ int i ; public : C(int i):i(i){cout << "C(" << i << ")\n";} void display() const { cout << "#C(" << i << ")\n"; } };
class D{ int i ; public : D(int i):i(i){cout << "D(" << i << ")\n";} void display() const { cout << "#D(" << i << ")\n"; } };
class E{ int i ; public : E(int i):i(i){cout << "E(" << i << ")\n";} void display() const { cout << "#E(" << i << ")\n"; } };

In your code here, we can see that all these classes have a common interface (understand here, a common "shape", they all have a void display() const method.

We could generalize all these class as the "same" as this one (at least if the logic inside their method is ignored)

class displayable {
    public:
        void display() const;
};

Now, this will be a common "type". However, we want all three class to be able to either implement, or override the function display. let's change this:

class i_displayable {
    public:
        virtual ~i_displayable(){}
        virtual void display() const = 0;

};

class displayable {
    public:
        virtual ~displayable() {}
        virtual void display() { std::cout << "displayable with default implementation" << std::endl;}
};

So, what is the differences between those two: i_displayable declare display as a pure virtual member. What that mean is that any class inheriting from i_displayable will have to implement display()

displayable declare display() as a virtual member, and provide an implementation. This will allow inheriting class to override the function.

I'll get to why both declare a virtual destructor in an instant.

Let's rewrite class C for now.

class C : public displayable { 
    int i;
    public:
        C(int i): i(i) { std::cout << "C(" << i >> ")" << std::endl;}
        virtual ~C(){}

        void display() const override {
            std::cout << "#C(" << i << ")" << std::endl;
        }
}

So, we've override the display function (the override keyword is optionnal), and we've said that C inherits from displayable publicly.

What this means is that we can now consider any pointer to a class C instance as a pointer to a displayable instance. As the function display is marked as virtual, when using a pointer do displayable, the function from C will be called, if it exists, and the one in displayable will be called if it does not.

That actually the reason behind making the destructor virtual. You don't want do destruct a displayable, but the actual instance (C in our case)

Now, let's say you've done this on C, D and E, your calling code can be rewritten as:

std::shared_ptr<displayable> f(int i){ 
    std::shared_ptr<displayable> res;

    if( i==1 ) { res = std::make_shared<C>(1); }
    else if( i==2 ) { res = std::make_shared<D>(2); }
    else { res = std::make_shared<E>(i); 

    return res;
}

int main(){
    cout << "/** trace\n\nusing union\n";{
        std::shared_ptr<displayable> res = f(1);
        res->display();
    }
}
Phantomas
  • 206
  • 1
  • 6
0

Using Try Catch based Exceptional Handling seems coding-friendly in most cases

There are no conditional statements needed for run time exception handling using try-catch like your code shows. I would personally use Exceptions using Try Catch as you did.

try{
        g(1);
    }
    catch(const C& c){ c.display(); }
    catch(const D& d){ d.display(); }
    catch(const E& e){ e.display(); }
Rahul Shyokand
  • 1,255
  • 10
  • 17
  • Thanks! However I'm afraid that if it's only a question of esthetics, I might have posted a troll-like question. sorry for that :( ..I'm still quite new to C++ – chetzacoalt Jul 24 '20 at 08:45