TransWikia.com

c++ should I prefer union or exception

Stack Overflow Asked by chetzacoalt on December 25, 2021

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 << "/** tracennusing unionn";{
        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 exceptionsn";{
        try{
            g(1);
        }
        catch(const C& c){ c.display(); }
        catch(const D& d){ d.display(); }
        catch(const E& e){ e.display(); }
    }cout << "nstopn*/n";
}

and here it the (obvious) trace I get

/** trace

using union
C(1)
#C(1)

using exceptions
C(1)
#C(1)

stop
*/

2 Answers

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 << "/** tracennusing unionn";{
        std::shared_ptr<displayable> res = f(1);
        res->display();
    }
}

Answered by Phantomas on December 25, 2021

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(); }

Answered by Rahul Shyokand on December 25, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP