10

假设我有这个异常类:

struct MyException : public std::exception
{
    MyException(const std::exception &exc) : std::exception(exc)
    {
        cout << "lval\n";
    }
    MyException(std::exception &&exc) : std::exception(std::forward<std::exception>(exc))
    {
        cout << "rval\n";
    }
};

...
...

try
{
    throw std::exception("Oh no!");
    // above is rvalue since it's got no name, what if the throw is made as
    // std::exception lvalExc("Oh wierd!");
    // throw lvalExc;
    // if the throw is made thus, how can it be caught by catch(std::exception &&exc)?
}
catch(std::exception &&rValRef)
{
    cout << "rValRef!\n";
    throw MyException(std::forward<std::exception>(rValRef));
}

当我试图通过值或(const) lvalue ref 捕获时。编译器说这些情况已经由 rvalue refcatch子句处理,这是可以理解的,因为异常是xvalue,也许捕获 xvalue 的最佳方法是 rvalue ref(如果我错了,请纠正我)。但是有人可以解释一下上述异常创建情况下的完美转发吗?这是对的吗?即使它编译,它是否有意义或有用?我使用的 C++ 库是否应该为其实现移动构造函数以std::exception使这种用法真正有意义?我尝试搜索有关异常的右值引用的文章和 SO 问题,但找不到任何问题。

4

1 回答 1

9

实际上,异常处理对于左值和右值有特殊的规则。临时异常对象是一个左值,参见当前草案的 15.1/3:

throw 表达式初始化一个临时对象,称为异常对象,其类型是通过从 throw 操作数的静态类型中删除任何顶级 cv 限定符并从“T 数组”或“返回 T 的函数”分别指向“指向 T 的指针”或“指向返回 T 的函数的指针”。临时值是一个左值,用于初始化匹配处理程序 (15.3) 中命名的变量。如果异常对象的类型是不完整类型或指向除(可能是 cv 限定的)void 以外的不完整类型的指针,则程序格式错误。除了这些限制和 15.3 中提到的类型匹配限制之外,throw 的操作数在调用 (5.2.2) 或 return 语句的操作数中被完全视为函数参数。

通过右值引用捕获也是非法的,参见 15.3/1:

处理程序中的异常声明描述了可能导致进入该处理程序的异常类型。异常声明不应表示不完整类型或右值引用类型。异常声明不应表示指向不完整类型的指针或引用,除了 void*、const void*、volatile void* 或 const volatile void*。

此外,您似乎不了解完美转发。您的前向调用并不比移动更好。完美转发的思想是将参数的值类别编码为类型的一部分,并让模板参数推导计算出来。但是您的异常处理程序不是也不能是函数模板。

基本上,完美转发依赖于模板参数推导和右值引用:

void inner(const int&);  // #1 takes only lvalues or const rvalues
void inner(int&&);       // #2 takes non-const rvalues only

template<class T>
void outer(T && x) {
    inner(forward<T>(x));
}

int main() {
   int k = 23;
   outer(k);   // outer<T=int&> --> forward<int&> --> #1
   outer(k+2); // outer<T=int>  --> forward<int>  --> #2
}

根据参数的值类别,模板参数推导将 T 推导出为左值引用或普通值类型。由于引用折叠,T&&在第一种情况下也是左值引用,或右值第二种情况参考。如果你看到 T&& 并且 T 是一个可以推导出的模板参数,它基本上是一个“抓住一切”。std::forward 恢复原始值类别(以 T 编码),因此我们可以完美地将参数转发给重载的内部函数并选择正确的函数。但这只是因为 outer 是一个模板并且因为有特殊的规则来确定 T 相对于它的值类别。如果您使用没有模板/模板参数推导的右值引用(如 #2 中),则该函数将只接受右值。

于 2010-10-05T03:34:42.130 回答