Определить, нет ли конструктора абстрактного базового класса?
В С++ 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 С++ или незавершенное производство также приветствуются.
EDIT: Как было указано дважды, по умолчанию конструкторы Derived
с = default
наследуют noexcept
. Но это не решает проблему для общего случая.
Ответы
Ответ 1
На основе skypjack answer лучшим решением, которое не требует изменения подписи конструктора Derived
, было бы определение макетного подкласса Base
как частного типа Derived
, и используйте конструкцию в конструкторе Derived
конструктор noexcept
:
class Derived: Base {
private:
struct MockDerived: Base {
using Base::Base;
// Override all pure virtual methods with dummy implementations:
int f() override; // No definition required
};
public:
template <typename ... Args>
Derived(Args && ... args)
noexcept(noexcept(MockDerived(std::forward<Args>(args)...)))
: Base(std::forward<Args>(args)...)
, m_f(g())
{}
int f() override { return 42; } // Real implementation
int m_f;
};
Ответ 2
[ОБНОВЛЕНИЕ: ЭТО ПРОДОЛЖЕНИЕ В РАЗДЕЛЕ ИЗМЕНЕНИЯ]
Хорошо, я нашел решение, хотя он не компилируется со всеми компиляторами из-за ошибки в GCC (см. этот вопрос для получения дополнительной информации).
Решение основано на наследуемых конструкторах и способах вызова функций.
Рассмотрим следующий пример:
#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
.
Здесь вы можете увидеть вышеупомянутый рабочий пример.
[EDIT]
Как из комментариев, пример страдает проблемой, если конструкторы базового класса и производного имеют одну и ту же подпись.
Спасибо Петру Скотницкому за то, что он указал на это.
Я собираюсь упомянуть эти комментарии, и я скопирую и вставлю предложенный вместе с ними код (с упоминанием авторов, где это необходимо).
Прежде всего, здесь, мы можем видеть, что пример как есть не работает должным образом (спасибо Петру Скотницки за ссылку).
Код почти такой же, как и раньше, поэтому его не стоит копировать и вставлять здесь.
Кроме того, из того же автора следует пример который показывает, что такое же решение работает как ожидалось при определенных обстоятельствах (см. Комментарии для дальнейших подробностей ):
#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
, то же самое, предложенное Петром Скотницким в комментарии:
достаточно, если конструктор производного класса выигрывает при разрешении перегрузки.
Ниже приведен код здесь:
#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;
}
Спасибо еще раз Петру Скотницкому за его помощь и комментарии, по достоинству оценили.
Ответ 3
Наивным, но рабочим примером будет введение не виртуального базового класса и экспорт его конструктора с помощью директивы 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();
}
Ответ 4
Я столкнулся с той же проблемой, и решение, которое я нашел, состояло в том, чтобы реализовать дополнительные черты.
Вы можете посмотреть мой пост здесь.