我有一个工会的用例,但是我对许多程序员使用工会很不舒服。因此,我尝试使用异常处理方式,而不是按照预期的方式使用。我知道这会由于处理异常而造成一些时间上的损失。我的问题是:有没有一种清洁的方法?
这是代码,带有联合,没有

//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";
}
这是我得到的(明显)跟踪
/** trace

using union
C(1)
#C(1)

using exceptions
C(1)
#C(1)

stop
*/

最佳答案

我强烈建议不要为此使用异常,因为它易于出错,而不是它们的含义。
问题之一是可扩展性。使用异常,假设您添加了一种类型,则必须确保已将其添加到try-catch语句中。而且,这是个坏习惯,因为它破坏了通常的代码流。 (假设您在g()之后添加了一些内容,它将永远不会被调用。
另一个问题是,如果存在实际的异常,则在同一个catch块中将逻辑与错误处理混合在一起,这将变得更难阅读。或者,您可能已经在引发异常的同时使代码引发异常,这将完全停止执行。
如果要使用并集,则可以使用std::variant,或在枚举上使用switch语句(这比一个接一个地使用ifs更好。)
但是,在C++和大多数面向对象的语言中,有一种更好的方法可以使用继承来实现您想要的功能:

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"; } };
在这里的代码中,我们可以看到所有这些类都有一个通用的接口(interface)(在这里理解一个通用的“形状”,它们都具有void display() const方法。
我们可以将所有这些类概括为与此类“相同”(至少如果忽略其方法内部的逻辑)
class displayable {
    public:
        void display() const;
};
现在,这将是常见的“类型”。但是,我们希望这三个类都能够实现或覆盖功能显示。让我们更改一下:
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;}
};
那么,这两者之间有什么区别:i_displayable声明display为纯虚拟成员。这意味着任何从i_displayable继承的类都必须实现display()displayabledisplay()声明为虚拟成员,并提供实现。这将允许继承的类重写该函数。
我将了解为什么两者都立即声明一个虚拟析构函数。
让我们现在重写class C
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;
        }
}
因此,我们已经覆盖了显示功能(override关键字为optionnal),并且我们已经说过C公开地继承自displayable
这意味着我们现在可以将指向class C实例的任何指针视为指向displayable实例的指针。由于功能显示被标记为虚拟显示,因此使用指针do displayable时,将调用C中的函数(如果存在),而调用displayable中的函数(如果不存在)。
这实际上是使析构函数虚拟化的原因。您不想破坏displayable,但要破坏实际实例(本例中为C)
现在,假设您已经在CDE上完成了此操作,那么您的调用代码可以重写为:
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();
    }
}

关于c++ - 我应该更喜欢c++还是exception,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/63069817/

10-11 15:46