c++ - c++ 我应该更喜欢联合还是异常
问题描述
我有一个联合的用例,但是和许多程序员一样,我觉得使用联合很难看。所以我尝试使用异常处理,而不是按预期的方式使用。我知道这会由于处理异常而导致一些时间损失。我的问题是:有没有一种干净的方法可以做到这一点?
这是代码,有联合,没有
//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"; } };
在你这里的代码中,我们可以看到所有这些类都有一个共同的接口(在这里理解,一个共同的“形状”,它们都有一个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
关键字是可选的),并且我们已经说过C
从 public 继承displayable
。
这意味着我们现在可以将任何指向class C
实例的指针视为指向实例的指针displayable
。由于函数 display 被标记为 virtual,当使用指针 dodisplayable
时,函数 fromC
将被调用,如果它存在,则调用 in ,displayable
如果它不存在。
这实际上是使析构函数虚拟化的原因。您不想破坏 a 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();
}
}
推荐阅读
- javascript - select2选项空间未搜索
- java - 如何在基于spring注解的项目中提供数据源入口。
- javascript - 如何安全地访问 firebase 数据以自动填充网页?
- python - Groupby和具有相同值的多列的总和
- apache-kafka - Kafka 集群在高输入时流超时
- java - TAG 和 UpdateUI 在 LoginActivity.java 的 FragmentActivity 错误中具有私有访问权限
- ruby-on-rails - Rails 5.x:如何在运行时添加路由而不覆盖原始路由表?
- php - 在 wordpress 中重复发布时出现 503 错误
- react-native - 在 react-native expo 应用程序中使用初始化 firestore 的正确方法
- c - 为什么在代码中没有返回值时,函数将值返回给前一个函数?