C++抛出类成员

8
我有以下的C++代码。
template <class E>
class ExceptionWrapper {
public:
    explicit ExceptionWrapper(const E& e): e(e) {}

    void throwException() {
        throw e;
    }

private:
    E e;
};

...
try {
    ExceptionWrapper<E> w(...);
    w.throwException();
} catch (const E& e) {
    ...
}
...

问题:这段代码有效吗?我可以争辩返回对类成员的引用几乎总是无效的(我相信每个人都同意这个说法)。然而,我的同事声称这在throw的情况下并非如此。

P.S. 将catch (const E& e)更改为catch (E e)后,一个令人讨厌的错误似乎消失了,这加强了我的立场——即这段代码是无效的


3
以上代码应该是有效的,而且引用捕获应该可以工作。 a)您的真实代码与上述代码不同吗? b)您正在使用哪个编译器? 抛出e 应该会抛出e的副本。 - Richard Hodges
1
一个真实的例子会更好。示例中缺少/错误太多了。 - luk32
@user2079303 是的,没错。抱歉,是我弄错了,已经做了修改。 - Prof. Legolasov
“我认为返回类成员的引用几乎总是无效的(我相信每个人都会同意这个说法)。我想说大多数getter函数都是这样做的。它们返回成员的const引用。” - luk32
你的同事们认为 throw 的哪一点不正确?除了使用 throw; 重新抛出异常时,没有语法可以通过引用抛出任何内容。 - eerorika
@user2079303 我的主张是,通过引用捕获e是无效的,因为e是w的成员,并且w在catch作用域中不活动。他们的主张是,在执行抛出时,e会以某种方式被复制。我不理解这一点,但准备一旦经过stackoverflow专业人士的批准就接受它。 - Prof. Legolasov
3个回答

7
我的观点是通过引用捕获e不是有效的,因为e是w的成员且w在catch范围内不活动。
您的说法是不正确的。throw e;抛出成员的副本,并且该副本在catch的范围内有效。
§ 15.1 / 3(n3797草案):
抛出异常会复制初始化(8.5,12.8)一个名为exception object的临时对象。该临时对象是lvalue,用于初始化匹配处理程序中命名的变量(15.3)。如果异常对象的类型是不完整的类型或指向不完整的类型的指针,而不是(可能的cv-qualified)void,则程序是非法的。评估具有运算数的throw-expression会抛出异常;从操作数的静态类型中删除任何顶级cv限定符,并将类型从“T数组”或“返回T的函数”调整为“指向T的指针”或“指向函数的指针返回T”,确定异常对象的类型。
通过const引用捕获是捕获异常的首选方法。它允许捕获std :: exception的派生类,而不会切片异常对象。

任何提到 C++ 标准并证实你观点的参考资料都足以让我接受你的答案并感谢你! - Prof. Legolasov
@Hindsight 如果想要快速了解,可以查看cppreference中throw的解释。首先,从表达式中复制初始化异常对象。如果我有时间,我会查看标准文档。 - eerorika
@Hindsight,这是标准引用。 - eerorika
将类型 E 的副本存储在 e 中,这样做是否也有可能存在切片问题? - chwarr
@c45207 当然,如果模板实例化时使用的是传递给它的对象的父类,那么可以这样做。但我认为这不是包装器的用例。如果是这样,那么它将不得不使用unique_ptr存储'e'。 - eerorika
显示剩余2条评论

2
我认为相关的观点是:

15.1. 抛出异常:

p3. throw表达式初始化一个临时对象,称为异常对象,其类型由从throw操作数的静态类型中删除任何顶层cv限定符并将类型从“T数组”或“返回T的函数”调整为“指向T的指针”或“指向返回T的函数的指针”,确定。 这个临时对象是一个左值,并用于初始化与匹配处理程序(15.3)中命名的变量。如果异常对象的类型是不完全类型或指向不完全类型(可能带有cv限定符)的指针,那么程序是非法的。除了这些限制和15.3中提到的类型匹配限制之外,throw操作数在调用中被视为函数参数(5.2.2)或return语句的操作数。

这来自于c++11草案,强调是我的。

基本上,它意味着从`throw`的参数中创建了一个临时对象。就像有一个函数`E f(){return private_e;}`,那个临时对象将用作适当处理程序的参数。因此,如果没有按引用捕获,您实际上将有两个可能的副本。
可能也相关的是:
当抛出对象是类对象时,即使省略了复制/移动操作(12.8),复制/移动构造函数和析构函数也必须是可访问的。

-4
如果构造函数失败/抛出异常,w就不存在了。因此,“w.throwException()”是无效的。

我不明白你的观点。构造函数不会失败/抛出异常。请将此代码视为学术示例。我不希望在问题描述之外听到任何与问题直接相关的评论。 - Prof. Legolasov
所示的构造函数不会抛出异常,即使它抛出了异常,也不会到达w.throwException();这一行。 - interjay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接