在C ++ 11及更高版本中,如何确定抽象基类的构造函数是否为noexcept
?以下方法不起作用:
#include <new>
#include <type_traits>
#include <utility>
struct Base { Base() noexcept; virtual int f() = 0; };
// static assertion fails, because !std::is_constructible<Base>::value:
static_assert(std::is_nothrow_constructible<Base>::value, "");
// static assertion fails, because !std::is_constructible<Base>::value:
static_assert(std::is_nothrow_default_constructible<Base>::value, "");
// invalid cast to abstract class type 'Base':
static_assert(noexcept(Base()), "");
// invalid new-expression of abstract class type 'Base'
static_assert(noexcept(new (std::declval<void *>()) Base()), "");
// cannot call constructor 'Base::Base' directly:
static_assert(noexcept(Base::Base()), "");
// invalid use of 'Base::Base':
static_assert(noexcept(std::declval<Base &>().Base()), "");
一个简单的用途是:
int g() noexcept;
struct Derived: Base {
template <typename ... Args>
Derived(Args && ... args)
noexcept(noexcept(Base(std::forward<Args>(args)...)))
: Base(std::forward<Args>(args)...)
, m_f(g())
{}
int f() override;
int m_f;
};
关于如何在不修改抽象基类的情况下如何实现这一点或是否可行的任何想法?
PS:也欢迎任何对ISO C ++缺陷报告或正在进行的工作的引用。
编辑:正如两次指出的那样,使用Derived
默认= default
构造函数会使noexcept
继承。但这并不能解决一般情况下的问题。
答案 0 :(得分:7)
[最新消息:他们非常期待编辑部分]
好的,我找到了一个解决方案,即使它没有编译所有编译器,因为GCC中存在错误(有关详细信息,请参阅this question)。
解决方案基于继承的构造函数和函数调用的解析方式 请考虑以下示例:
#include <utility>
#include <iostream>
struct B {
B(int y) noexcept: x{y} { }
virtual void f() = 0;
int x;
};
struct D: public B {
private:
using B::B;
public:
template<typename... Args>
D(Args... args)
noexcept(noexcept(D{std::forward<Args>(args)...}))
: B{std::forward<Args>(args)...}
{ }
void f() override { std::cout << x << std::endl; }
};
int main() {
B *b = new D{42};
b->f();
}
我猜它很清楚
无论如何,如果您发现某些内容需要更多细节,请告诉我。我很乐意更新答案。
基本思想是我们可以直接从基类继承noexcept
定义及其构造函数,这样我们就不再在noexcept
语句中引用该类了。
Here您可以看到上面提到的工作示例。
<强> [编辑] 强>
从注释中可以看出,如果基类的构造函数和派生类的构造函数具有相同的签名,则该示例会出现问题。
感谢Piotr Skotnicki指出了这一点
我将提及这些评论,并且我将复制并粘贴与他们一起提出的代码(在必要时提及作者)。
首先,here我们可以看到该示例不能正常工作(感谢Piotr Skotnicki的链接)。
代码几乎与之前发布的相同,因此在此处复制和粘贴它并不值得
此外,来自同一作者,它遵循一个示例that shows that the same solution works as expected under certain circumstances(请参阅有关其他详细信息的评论):
#include <utility>
#include <iostream>
struct B {
B(int y) noexcept: x{y}
{
std::cout << "B: Am I actually called?\n";
}
virtual void f() = 0;
int x;
};
struct D: private B {
private:
using B::B;
public:
template<typename... Args>
D(int a, Args&&... args)
noexcept(noexcept(D{std::forward<Args>(args)...}))
: B{std::forward<Args>(args)...}
{
std::cout << "D: Am I actually called?\n";
}
void f() override { std::cout << x << std::endl; }
};
int main()
{
D* d = new D{71, 42};
(void)d;
}
另外,我提出了一个更具侵入性的替代解决方案,它基于std::allocator_arg_t
代表的想法,这也是Piotr Skotnicki在评论中提出的相同:
如果派生类&#39;它足够了。构造函数在重载决策中获胜。
它遵循提到的代码here:
#include <utility>
#include <iostream>
struct B {
B(int y) noexcept: x{y}
{
std::cout << "B: Am I actually called?\n";
}
virtual void f() = 0;
int x;
};
struct D: public B {
private:
using B::B;
public:
struct D_tag { };
template<typename... Args>
D(D_tag, Args&&... args)
noexcept(noexcept(D{std::forward<Args>(args)...}))
: B{std::forward<Args>(args)...}
{
std::cout << "D: Am I actually called?\n";
}
void f() override { std::cout << x << std::endl; }
};
int main()
{
D* d = new D{D::D_tag{}, 42};
(void)d;
}
再次感谢Piotr Skotnicki的帮助和评论,非常感谢。
答案 1 :(得分:4)
基于skypjack's answer,不需要对Derived
构造函数进行签名更改的更好的解决方案是将Base
的模拟子类定义为{{1}的私有类型成员并使用Derived
构造函数Derived
规范中的构造:
noexcept
答案 2 :(得分:1)
一个天真但有效的例子是引入非虚基类并通过using
指令导出其构造函数。这是一个例子:
#include<utility>
struct BaseBase {
BaseBase() noexcept { }
};
struct Base: public BaseBase {
using BaseBase::BaseBase;
virtual int f() = 0;
};
struct Derived: public Base {
template <typename ... Args>
Derived(Args && ... args)
noexcept(noexcept(BaseBase(std::forward<Args>(args)...)))
: Base(std::forward<Args>(args)...)
{ }
int f() override { }
};
int main() {
Derived d;
d.f();
}
答案 3 :(得分:0)
我遇到了同样的问题,我找到的解决方案是实现额外的特性。
您可以查看我的帖子here。