Наследование конструкторов и перенаправление

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);
}

Кроме того, каждый экземпляр шаблона является отдельным конструктором (при использовании не умножается)

Другие вопросы по тегам