c++ - que - Determine si un constructor de una clase base abstracta es noexcept?
para que sirve super en java (4)
En C ++ 11 y noexcept
posteriores, ¿cómo determinar si un constructor de una clase base abstracta es noexcept
? Los siguientes métodos no funcionan:
#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()), "");
Un uso simple para esto sería:
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;
};
¿Alguna idea sobre cómo archivar esto o si es posible en absoluto sin modificar la clase base abstracta?
PS: Cualquier referencia a los informes de defectos de ISO C ++ o trabajos en curso también son bienvenidos.
EDITAR: Como se señaló dos veces, el = default
constructores Derived
con = default
hace que noexcept
se herede. Pero esto no resuelve el problema para el caso general.
Basado en la respuesta de skypjack, una mejor solución que no requiera un cambio de firma del constructor Derived
sería definir una subclase simulada de Base
como un miembro de tipo privado Derived
, y usar la construcción de eso en la especificación de noexcept
constructor 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;
};
Estaba enfrentando el mismo problema y la solución que encontré fue implementar rasgos adicionales.
Puedes echar un vistazo a mi post here .
Un ejemplo ingenuo pero de trabajo sería introducir una clase base no virtual y exportar su constructor por medio de la directiva using
. Aquí hay un ejemplo:
#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();
}
[Actualización: vale la pena saltar a la sección de edición ]
Ok, encontré una solución, aunque no se compila con todos los compiladores debido a un error en GCC (consulte esta pregunta para obtener más información).
La solución se basa en constructores heredados y la forma en que se resuelven las llamadas de funciones.
Considere el siguiente ejemplo:
#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();
}
Supongo que es bastante claro.
De todos modos, avíseme si encuentra que algo necesita más detalles y estaré encantado de actualizar la respuesta.
La idea básica es que podemos heredar directamente la definición noexcept
de la clase base junto con sus constructores, de modo que ya no tengamos que referirnos a esa clase en nuestras declaraciones noexcept
.
Here puede ver el ejemplo de trabajo mencionado anteriormente.
[EDITAR]
A partir de los comentarios, el ejemplo sufre de un problema si los constructores de la clase base y el derivado tienen la misma firma.
Gracias a Piotr Skotnicki por haberlo señalado.
Voy a mencionar esos comentarios y copiaré y pegaré el código propuesto junto con ellos (con una mención de los autores donde sea necesario).
En primer lugar, here podemos ver que el ejemplo no funciona como se esperaba (gracias a Piotr Skotnicki por el enlace).
El código es casi el mismo publicado anteriormente, por lo que no vale la pena copiarlo y pegarlo aquí.
Además, del mismo autor, sigue un ejemplo que muestra que la misma solución funciona como se esperaba en ciertas circunstancias (consulte los comentarios para obtener más detalles):
#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;
}
Además, propongo una solución alternativa que es un poco más intrusiva y se basa en la idea que significa std::allocator_arg_t
, que también es la misma propuesta por Piotr Skotnicki en un comentario:
es suficiente si el constructor de la clase derivada gana en resolución de sobrecarga.
Sigue el código mencionado 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;
}
Gracias una vez más a Piotr Skotnicki por su ayuda y comentarios, realmente apreciado.