C++ 空vs抛出与性能

C++ 空vs抛出与性能,c++,performance,exception,null,C++,Performance,Exception,Null,我有一门课: class Vector { public: element* get(int i); private: element* getIfExists(int i): }; get调用getIfExists;若元素存在,则返回该元素,若不存在,则执行某些操作getIfExists可以表示某个元素i不存在 通过抛出异常或返回NULL 问:在表现上会有什么不同吗?在一种情况下,get需要检查==NULL,在另一种情况下,尝试。。。捕捉 是的,性能会有所不同

我有一门课:

class Vector {
    public:
    element* get(int i);
    private:
    element* getIfExists(int i):
};
get
调用
getIfExists
;若元素存在,则返回该元素,若不存在,则执行某些操作
getIfExists
可以表示某个元素
i
不存在 通过抛出异常或返回NULL


问:在表现上会有什么不同吗?在一种情况下,
get
需要检查
==NULL
,在另一种情况下,
尝试。。。捕捉

是的,性能会有所不同:返回NULL比抛出异常花费更少,检查NULL比捕获异常花费更少


附录:但只有在您预计这种情况会频繁发生的情况下,性能才是相关的,在这种情况下,它可能不是例外情况。在C++中,使用异常来实现正常程序逻辑是一种坏的风格,这似乎是:我假设获取的点是在必要时自动扩展向量。

< P>当然,性能会有所不同(如果你给向量:甚至是非常大的一个:):GETIFE存在< /COD> >代码>规范,但我在这里有点猜测)。但在我看来,这是因为树木而错过了森林

钱的问题是:你会用越界参数多次调用这个方法吗?如果是,原因是什么?

(请参阅评论!)

毫无疑问,
returnnull
变量具有更好的性能

如果可以使用返回值,则通常不应使用异常。 由于该方法名为
get
,我假设NULL不是有效的结果值,因此传递NULL应该是最好的解决方案。 如果调用方不测试结果值,它将取消引用null值,并呈现SIGSEGV,这也是合适的

如果很少调用该方法,则根本不应该关心微观优化

哪种翻译方法看起来更容易

$g++-Os-c test.cpp

#include <cstddef>

void *get_null(int i) throw ();
void *get_throwing(int i) throw (void*);

int one(int i) {
    void *res = get_null(i);
    if(res != NULL) {
        return 1;
    }
    return 0;
}

int two(int i) {
    try {
        void *res = get_throwing(i);
        return 1;
    } catch(void *ex) {
        return 0;
    }
}
#包括
void*get_null(inti)throw();
虚空*获得投掷(int i)投掷(虚空*);
int一(int一){
void*res=get_null(i);
如果(res!=NULL){
返回1;
}
返回0;
}
整数二(整数一){
试一试{
void*res=get_投掷(i);
返回1;
}捕获(无效*ex){
返回0;
}
}
$objdump-dC test.o

0000000000000000 <one(int)>:
   0:   50                      push   %rax
   1:   e8 00 00 00 00          callq  6 <one(int)+0x6>
   6:   48 85 c0                test   %rax,%rax
   9:   0f 95 c0                setne  %al
   c:   0f b6 c0                movzbl %al,%eax
   f:   5a                      pop    %rdx
  10:   c3                      retq   

0000000000000011 <two(int)>:
  11:   56                      push   %rsi
  12:   e8 00 00 00 00          callq  17 <two(int)+0x6>
  17:   b8 01 00 00 00          mov    $0x1,%eax
  1c:   59                      pop    %rcx
  1d:   c3                      retq   
  1e:   48 ff ca                dec    %rdx
  21:   48 89 c7                mov    %rax,%rdi
  24:   74 05                   je     2b <two(int)+0x1a>
  26:   e8 00 00 00 00          callq  2b <two(int)+0x1a>
  2b:   e8 00 00 00 00          callq  30 <two(int)+0x1f>
  30:   e8 00 00 00 00          callq  35 <two(int)+0x24>
  35:   31 c0                   xor    %eax,%eax
  37:   eb e3                   jmp    1c <two(int)+0xb>
0000000000000000:
0:50推力%rax
1:e8 00 00 callq 6
6:48 85 c0测试%rax,%rax
9:0f 95 c0设定值%al
c:0f b6 c0 movzbl%al,%eax
f:5a pop%rdx
10:c3 retq
0000000000000011 :
11:56%rsi
12:e8 00 00 callq 17
17:b8 01 00 mov$0x1,%eax
1c:59%持久性有机污染物循环利用率
1d:c3 retq
1e:48日约12%rdx
21:48 89 c7 mov%rax,%rdi
24:74 05
26:e8 00 00 callq 2b
2b:e8 00 00呼叫Q 30
30:e8 00呼叫35
35:31 c0异或%eax,%eax
37:eb e3 jmp 1c

这是设计问题,而不是性能问题。如果这是一个异常情况,比如在
get
函数中,那么抛出一个异常;或者更好地触发断言,因为违反函数先决条件是编程错误。如果这是一个预期的情况,比如在
getIfExist
函数中,那么不要抛出异常


关于性能,存在零成本异常实现(尽管并非所有编译器都使用该策略)。这意味着只有在抛出异常时才会支付开销,异常应该是。。。好。。。异常情况。

现代编译器实现“零成本”异常-它们只在抛出时产生成本,成本与清除加上缓存未命中以获得要清理的列表成比例。因此,如果异常是异常的,它们确实可以比返回代码更快。如果它们是普通的,它们可能会慢一些。如果您的错误在函数调用中的函数中,它实际上做的工作要少得多。这些细节很吸引人,值得谷歌搜索

但成本非常低。在一个紧密的循环中,它可能会有所不同,但通常不会


您应该编写最容易推理和维护的代码,然后对其进行分析,只有在遇到瓶颈时才重新考虑您的决定。

如果调用者希望处理项目不存在的可能性,您应该以一种指示返回的方式返回,而不引发异常。如果调用方没有准备好,您应该抛出一个异常。当然,被调用的例程不太可能神奇地知道调用方是否准备好应付麻烦。需要考虑的几种方法:

  • Microsoft的模式是使用Get()方法,如果对象存在,则返回该对象;如果对象不存在,则抛出异常;使用TryGet()方法,则返回一个布尔值,指示对象是否存在,并将对象(如果存在)存储到Ref参数。我对这种模式最大的不满是使用它的接口不能是协变的。
  • 我通常更喜欢引用类型集合的一种变体是使用Get和TryGet方法,对于不存在的项使用TryGet返回null。界面协方差以这种方式工作得更好。
  • 上面的一个细微变化是让TryGet方法通过引用接受布尔值,并将成功/失败指示器存储到该布尔值,这一变化甚至适用于值类型或无约束泛型。如果失败,代码可以返回适当类型的未指定对象(很可能是默认值)。
  • 阿诺