继承构造函数和虚基类

6

我即将创建一个异常类层次结构,概念上看起来有点像这样:

#include <iostream>
#include <stdexcept>

class ExceptionBase : public std::runtime_error {
public: 
    ExceptionBase( const char * msg ) : std::runtime_error(msg) {}
};

class OperationFailure : virtual public ExceptionBase {
public: 
    using ExceptionBase::ExceptionBase;
};

class FileDoesNotExistError : virtual public ExceptionBase {
public: 
    using ExceptionBase::ExceptionBase;
};

class OperationFailedBecauseFileDoesNotExistError
    : public OperationFailure, FileDoesNotExistError {
public: 
    using ExceptionBase::ExceptionBase; // does not compile
};

int main() {
    OperationFailedBecauseFileDoesNotExistError e("Hello world!\n");

    std::cout << e.what();
}

所有的构造函数都应该和 ExceptionBase 类的构造函数一样。派生异常只有类型不同,除此之外没有其他的功能增加。以上代码中提到的最后一个异常类型也应该有这些构造函数。使用C++11标准的继承构造函数特性是否可以实现这一点?如果不可能,还有哪些替代方案?
(顺便说一句:在上面的代码中,OperationFailureFileDoesNotExistError 类在gcc 4.8下无法编译,但在clang 3.4下可以编译通过。显然,gcc拒绝为虚基类继承构造函数。很有趣的是,两个编译器都拒绝了OperationFailedBecauseFileDoesNotExistError类,因为继承构造函数没有从直接基类继承。)

3
菱形继承?太棒了。 - littleadv
1
3,2,1... 开始乐趣之旅! - Adri C.S.
也许你需要使用 using OperationFailure::OperationFailure?但是由于双重继承的原因,可能不起作用。 - Albert
即使是最近的g++4.9版本,在创建对象(使用参数化构造函数)时也无法编译“OperationFailure”。当将虚继承替换为非虚继承时,它可以工作。我建议您向gcc提交错误报告,因为我在标准中找不到任何禁止此操作的内容,而最新的提案N2540明确允许这样做。 - dyp
我会提交一个错误报告。 - Ralph Tandetzky
2个回答

2
使用using-declaration来继承构造函数时,它需要直接基类 [namespace.udecl]/3。
如果这样的using-declaration命名了一个构造函数,则嵌套名称限定符必须命名正在定义的类的直接基类;否则,它会引入通过成员名称查找找到的声明集合。
也就是说,在您的情况中,OperationFailedBecauseFileDoesNotExistError中的using-declaration不是继承,而是重新声明(作为别名)或取消隐藏ExceptionBase的ctor的名称。
因此,您将需要为OperationFailedBecauseFileDoesNotExistError编写一个非继承的ctor。
顺便说一下,这对于非虚拟基类是可以的:用于继承构造函数的using-declaration被重写为:
//using ExceptionBase::ExceptionBase;

OperationFailure(char const * msg)
: ExceptionBase( static_cast<const char*&&>(msg) )
{}

由于你只能在 mem-initializer-list 中初始化直接基类(或虚基类),因此对于非虚基类,将 using-declaration 限制为仅从直接基类继承构造函数是有意义的。

继承构造函数提案的作者已经意识到这会破坏对虚基类构造函数的支持,请参见N2540

通常情况下,对于具有虚基类的类的继承构造函数定义将是不合法的,除非虚基类支持默认初始化,或虚基类是直接基类,并作为被转发的基类命名。同样,所有数据成员和其他直接基类必须支持默认初始化,否则任何尝试使用继承构造函数都将是不合法的。注意:使用时不合法,而不是声明时。


1
继承构造函数就像为您指定的所有构造函数引入包装器函数。在您的情况下,您必须调用OperationFailureFileDoesNotExistError的特定构造函数,但引入的包装器只会调用其中之一。
我刚刚查看了C++11最新草案(第12.9节),但它并没有明确涵盖你的情况。

using-declaration并不会抑制默认构造函数的隐式声明,因此OperationFailureFileDoesNotExistError仍然具有默认构造函数。但我不确定它们是否良好定义,因为它们没有正确地初始化虚基类。 - dyp
如果在 OperationFailedBecauseFileDoesNotExistError(我们称之为类 O)中的这个 using-declaration 合法,它将引入一个等同于 O(char const* msg) : ExceptionBase( static_cast<char const*&&>(msg) ) {} 的构造函数。然而,这个构造函数是不合法的,因为它调用了两个直接基类的删除默认构造函数。后者是不合法的(被删除),因为它们没有初始化虚拟基类,请参见 [class.ctor]/5。 - dyp
请参考DR1567获取更多相关编程信息。 - dyp

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