Наследование конструкторов и перенаправление
C++11 позволяет наследовать конструкторы, что позволяет избежать большого количества шаблонов, особенно с чем-то вроде класса-оболочки. Тем не менее, кажется, что вы могли бы достичь этой функциональности только с помощью шаблонов с переменным числом аргументов.
class B
{
public:
B(int){//do something}
B(int, char){//do something}
};
Использование наследующих конструкторов:
class D : public B
{
public:
using B::B;
};
Использование вариадических шаблонов и вперед:
class D : public B
{
public:
template <typename...Args>
D(Args&&... args) : B(std::forward<Args>(args)...)
{
}
};
При согласованности (относитесь к конструкторам и методам одинаково для using
) и простота использования - очень веские причины для переноса унаследованных конструкторов в язык. Есть ли какая-либо другая причина, по которой первое решение должно быть предпочтительнее второго? Обе документы CWG ( N1890 и N1898), которые я обнаружил, обсуждая конструкторы наследования, просто отмечают следующее и продолжают:
Немного больше, чем историческая случайность, мешает использовать это для работы как для конструктора, так и для обычной функции-члена. Если бы конструктор назывался "ctor" или "конструктор", а не назывался именем их класса, это работало бы. Мы предлагаем это как механизм наследования конструкторов.
3 ответа
Основная причина в том, что идеальная пересылка не идеальна. Простой случай:
struct B {
B(int* ) { .. }
};
Теперь рассмотрим:
D d{0};
Если мы наследуем конструктор, это работает нормально. Если мы совершаем вперёд, мы постараемся построить B(int )
, поскольку 0
выводит как int
, который является конструктором, который не существует. Провал!
Другие случаи сбоев включают в себя инициализацию в фигурных скобках, статические члены-константы только для объявлений, перегруженные функции и битовые поля.
Идеальная пересылка не совсем идеальна. В частности, 0 в качестве фактического аргумента сводится к int
вместо того, чтобы обозначать общее нулевое значение. Так с конструктором, принимающим аргумент указателя, 0
будет работать как фактический аргумент для унаследованного конструктора, но не для пересылки.
Еще одна причина, которую, как мне кажется, я упомянул в свое время, когда был задан вопрос (действительно ли нам нужны наследуемые конструкторы), заключается в том, что наследующие конструкторы - это простая запись концептуально простой вещи, которая должна использоваться в контексте простого класса. В C++ достаточно принудительной сложности и шаблонного шаблона.
Проблемы обслуживания (ошибки компилятора) могут привести к предпочтению использования:
struct Base
{
Base(int) {}
};
struct Derived : public Base
{
using Base::Base;
};
struct DerivedTemplate : Base
{
template <typename...Args>
DerivedTemplate(Args&&... args)
: Base(std::forward<Args>(args)...)
{}
};
int main()
{
// error: no matching function for call to ‘Derived::Derived(int, int)’
Derived d(1, 2);
// In instantiation of ‘DerivedTemplate::DerivedTemplate(Args&& ...) [with Args = {int, int}]’:
// required from here
// error: no matching function for call to ‘Base::Base(int, int)’
DerivedTemplate dt(1, 2);
}
Кроме того, каждый экземпляр шаблона является отдельным конструктором (при использовании не умножается)