我有一个工会的用例,但是我对许多程序员使用工会很不舒服。因此,我尝试使用异常处理方式,而不是按照预期的方式使用。我知道这会由于处理异常而造成一些时间上的损失。我的问题是:有没有一种清洁的方法?
这是代码,带有联合,没有
//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()
displayable
将display()
声明为虚拟成员,并提供实现。这将允许继承的类重写该函数。我将了解为什么两者都立即声明一个虚拟析构函数。
让我们现在重写
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
)现在,假设您已经在
C
,D
和E
上完成了此操作,那么您的调用代码可以重写为: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/