不明确的虚拟不和谐

时间:2016-07-18 00:45:00

标签: c++ c++11 c++14

对于应用程序,我需要创建一组特殊类来处理异常。我从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;
}

代码编译但不起作用。知道如何解决这个问题吗?

2 个答案:

答案 0 :(得分:3)

我现在可以看到两个问题:

1:最令人烦恼的解析

正如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的早期版本是否存在问题。]

2:钻石问题

我认为您遇到问题的代码是两条注释行:

//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::exceptionstd::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;
}