对于应用程序,我需要创建一组特殊类来处理异常。我从std :: exception派生了我的基类。然而,我最终面临钻石问题和模棱两可的继承。即使使用虚拟继承也没有帮助。以下示例演示了此问题。
#include <iostream>
class Exception:
public virtual std::exception
{
public:
Exception():
std::exception("This is default")
{
std::cout << "This is Exception\n";
}
};
class ChildException:
public virtual std::runtime_error,
public Exception
{
public:
ChildException():
Exception(),
std::runtime_error("hello")
{
std::cout << "This is ChildException\n";
}
};
int main()
{
ChildException exc();
//std::cout << static_cast<std::exception> (exc).what() << std::endl;
//std::cout << static_cast<std::runtime_error> (exc).what() << std::endl;
getchar();
return 0;
}
代码编译但不起作用。知道如何解决这个问题吗?
答案 0 :(得分:3)
我现在可以看到两个问题:
正如Brian在评论中指出的那样,这一行实际上是一个函数原型:
ChildException exc();
可以将其视为通过调用默认构造函数初始化的ChildException
exc
,或者作为返回exc
的名为ChildException
的函数读取;我不确定具体原因,但C ++标准规定在这种情况下,它将被视为后者。
有三种方法可以解决这个问题:
删除括号:如果您只是调用默认构造函数,则可以在没有括号的情况下编写它。然而,这并不总是一个选项,因为当你尝试使用函数调用获得的值进行直接初始化时,最令人烦恼的解析也会让你措手不及。
ChildException exc;
// Most vexing parse:
ChildException ce;
ChildException ce2(ce);
// This is safe, it can't be read as a function prototype.
ChildException ce3(ChildException());
// This will be parsed as a function with:
// Return type: ChildException
// Parameter: Function pointer of type "ChildException (*)()".
使用复制初始化:您可以使用赋值语法对其进行初始化,编译器将通过复制省略来优化它。
ChildException exc = ChildException();
这样可行,但看起来不必要笨重,并且如果您遇到无法执行复制省略的编译器,则可能会降低效率。
使用统一初始化:从C ++ 11开始,当使用支持统一初始化*的编译器时,可以使用大括号而不是括号来指定构造函数调用;考虑到问题的标签,我会推荐这种方法。
ChildException exc{};
* [在三个“最大”编译器中,Clang 3.1或更高版本,GCC 4.6及更高版本以及Visual Studio 2013及更高版本支持统一初始化。虽然GCC在4.4版本支持它,而Visual Studio在2012年CTP支持它,但早期版本在某些情况下有困难;我不确定Clang的早期版本是否存在问题。]
我认为您遇到问题的代码是两条注释行:
//std::cout << static_cast<std::exception> (exc).what() << std::endl;
//std::cout << static_cast<std::runtime_error> (exc).what() << std::endl;
或者更具体地说,问题是这些行中的第一行导致“模糊转换”错误,而第二行正常工作。这是因为ChildException
实际上有两个 std::exception
基类,每个基类都与另一个基本类别。类布局看起来像这样,具体来说:
class ChildException size(28):
+---
| +--- (base class Exception)
0 | | {vbptr}
| +---
+---
+--- (virtual base runtime_error)
| +--- (base class exception)
4 | | {vfptr}
8 | | _Mywhat
12 | | _Mydofree
| | <alignment member> (size=3)
| +---
+---
+--- (virtual base exception)
16 | {vfptr}
20 | _Mywhat
24 | _Mydofree
| <alignment member> (size=3)
+---
请注意,如果您愿意,那么Exception
实际上继承自std::exception
,std::runtime_error
则不会。因此,其std::exception
基数与您的Exception
std::exception
基数不同,因此任何将ChildException
转换为std::exception
的尝试都不明确,因为它可以指代ChildException::Exception::exception
基础或ChildException::runtime_error::exception
基础。如果可能,我建议重构您的异常类,以便每个类最多只从一个std
异常类继承。如果无法做到这一点,您可以通过其中一个基类强制转换它:
// Cast into std::exception through the base classes:
std::cout << "As Exception: "
<< static_cast<std::exception>(static_cast<Exception>(exc)).what()
<< std::endl;
std::cout << "As runtime_error: "
<< static_cast<std::exception>(static_cast<std::runtime_error>(exc)).what()
<< std::endl;
由于钻石问题引起的问题,不建议这样做,但必要时可以使用。这是因为其中每个都将访问不同的 std::exception
:第一个将在类'布局结束时访问virtual
,而第二个将访问一个在std::runtime_error
。
答案 1 :(得分:0)
这是一个解决钻石问题的不同解决方案。在此解决方案中,基本Exception类不是从std :: exception派生的。相反,它包含一个指向std :: exception的指针。此外,它有两个转换器函数,使用其指针将Exception转换为std :: exception。派生类使用std :: runtime_error初始化指针。使用这种方法,解决了歧义,可以将一个ChildException对象捕获为std :: exception或std :: runtime_error。
#include <iostream>
class Exception
{
public:
explicit Exception()
{
pSTDException = nullptr;
}
operator std::exception*()
{
return pSTDException;
}
operator std::exception()
{
return * pSTDException;
}
std::exception * pSTDException;
};
class ChildException:
public std::runtime_error,
public Exception
{
public:
ChildException():
std::runtime_error("This is ChildException")
{
this->pSTDException = static_cast<std::exception *>(static_cast<std::runtime_error *>(this));
}
};
int main()
{
try
{
throw ChildException();
}
catch(std::exception & e)
{
std::cout << e.what();
}
getchar();
return 0;
}