2016-10-11 4 views
0

У меня есть два класса: Base и Derived. Derived наследует все конструкторы Base. Кроме того, у меня есть шаблонный класс Printer<T>, который содержит ссылку на объект типа T и имеет метод print(), который каким-то образом печатает объект. Вот минимальная иллюстрация.Установка конструктора по умолчанию для одного типа с множественным наследованием

class Base { 
public: 
    Base(int x) : x(x) {} 
    int x; 
}; 

template<typename T> 
class Printer { 
public: 
    const T& object; 

    Printer(const T& object) : object(object) {} 

    void print() { 
     cout << object << endl; 
    } 
}; 

class Derived : public Base { 
public: 
    using Base::Base; 
}; 

std::ostream& operator<<(std::ostream& out, const Derived& d) { 
    return out << d.x; 
} 

int main() { 
    Derived d(1); 

    Printer<Derived>(d).print(); 
} 

Теперь я хотел бы избежать прямого использования Printer и позволяют такой синтаксис: Derived d(1); d.print();. Таким образом, я попытался наследовать Derived также от Printer<Derived>.

class Derived : public Base, public Printer<Derived> { 
public: 
    typedef Printer<Derived> MyPrinter; 

    using Base::Base; 

    Derived() : MyPrinter(*this) {} 
}; 

Теперь у меня есть проблема: Base конструкторы ничего не знают о Printer и, таким образом, не может инициализировать его каким-либо образом. Я также не могу использовать делегирование конструктора здесь, потому что конструктор, который используется в Derived, фактически унаследован от Base.

Можно ли каким-либо образом сделать конструктор по умолчанию Derived делегированным любым другим конструктором, даже унаследованным? Или, может быть, есть несколько других шаблонов для инициализации второй базы в множественном наследовании?

Еще одна вещь, которая затвердевает, - это то, что у меня нет доступа к коду Base и он может использовать его как есть.

UPDATE На ответ Реми Лебо в: Base может иметь несколько конструкторов, которые я не в курсе (это шаблонный класс, а), так что я не могу реализовать все из них и должны использовать using Base::Base идиомы.

Ответ на krzaq: Printer На самом деле также существует множество методов, а не только print(), поэтому внедрение класса пересылки является неприятностью, и я стараюсь избегать его.

+1

Вы забываете о виртуальных деструкторов в ваших базовых классов. – PaulMcKenzie

+1

Nope. Конструктор может быть делегирован только другим конструктором того же класса. И это не имеет ничего общего с конструкторами. Даже если некоторые конструкторы могут быть делегированы, факт состоит в том, что интерфейс «Base» выгравирован на камне. Что есть, то есть. Если, как вы говорите, вы можете использовать только «Base», как есть », то единственное, что вы можете сделать, это переопределить любые методы в базе, которые являются виртуальными. Вы не можете добавлять к нему какие-либо методы. Конец. –

+0

@PaulMcKenzie Конечно, есть в реальном коде, я просто забыл о некоторых, делая этот искусственный пример. Благодарю. –

ответ

1

Если все, что вам нужно иметь доступ к Derived например, из Printer<Derived>, то вы можете просто бросить его вниз:

template<typename T> 
class Printer { 
public: 
    const T& object; 

    Printer() : object(static_cast<T&>(*this)) {} 

    void print() { 
     cout << object << endl; 
    } 
}; 

live demo

или покончить со ссылкой в ​​целом и сделать свой класс право на EBO:

template<typename T> 
class Printer { 
public: 
    void print() { 
     cout << static_cast<T&>(*this) << endl; 
    } 
}; 

live demo

Если вы не можете/не хотите трогать Printer либо, я бы создать отдельный шаблон для PrinterForwarder firward на print() вызов правой принтера:

template<typename T> 
class PrinterForwarder 
{ 
public: 
    void print() { 
     Printer<T>(static_cast<T&>(*this)).print(); 
    } 
}; 

class Derived : public Base, public PrinterForwarder<Derived> { 
public: 
    using Base::Base; 
}; 

live demo

+0

'Принтер' не всегда используется таким образом, он также может использоваться как отдельный класс (как в первом примере), и поэтому' static_cast' завершится с ошибкой. Возможно, я могу проверить с помощью 'dynamic_cast ', где наследование действительно имеет место, я попробую. –

+0

Хорошо, я думал, что первый пример был обходным путем, о котором вы хотели избавиться. Если вы этого не сделаете, я думаю, что я бы сделал отдельный принтер-форвардер, из которого вы могли бы получить, что бы переслать '* this' на принтер. – krzaq

+0

@IvanSmirnov Я отредактировал ответ. Надеюсь, это будет более полезно. – krzaq

1

Теперь у меня есть проблема: базовые конструкторы ничего не знают о принтере и поэтому не могут его инициализировать каким-либо образом.

Чтобы сделать то, что вы делаете, вы больше не сможете использовать инструкцию using Base::Base. Вы должны быть более четко о конструкторах, что Derived орудий, поэтому каждый из них может инициализировать Printer базовый класс по мере необходимости, например:

class Derived : public Base, public Printer<Derived> { 
public: 
    typedef Printer<Derived> MyPrinter; 

    Derived() : Base(0), MyPrinter(*this) {} 
    Derived(int x) : Base(x), MyPrinter(*this) {} 
}; 

Или:

class Derived : public Base, public Printer<Derived> { 
public: 
    typedef Printer<Derived> MyPrinter; 

    Derived() : Derived(0) {} 
    Derived(int x) : Base(x), MyPrinter(*this) {} 
}; 
+0

Это кажется неизбежным, но это не вариант, поскольку Base имеет много ctors, и я не могу их повторно реализовать. –

+1

Если конструктор 'Printer' требует входного значения, тогда все конструкторы в' Derived' должны передать ему значение, что означает повторное внедрение всех конструкторов, как показано выше. Но если вы перепроектируете «Принтер», чтобы не требовать входного значения в его конструкторе, как показал krzaq, вам больше не придется об этом беспокоиться. –

0

Я нашел удивительно простое решение. Сохраним в Printer указатель на T вместо ссылки.

template<typename T> 
class Printer { 
public: 
    const T* objectPtr; 

    Printer() : objectPtr(nullptr) {} 
    Printer(const T& object) : objectPtr(&object) {} 

    void print() { 
     if (objectPtr) { 
      cout << *objectPtr << endl; 
     } else { 
      cout << static_cast<const T&>(*this) << endl; 
     } 
    } 
}; 

Это выглядит не очень безопасно, но делает Printer() частный кажется т е р, чтобы сделать сделку.

+0

Без виртуальных функций и прикладов, не будет ли он вызывать неправильный оператор (для принтера, а не для T)? – RiaD

+0

Несомненно, это будет исправлено. Для этого, конечно же, нужно использовать «T». –

+1

Обратите внимание, что копировать/перемещать ctors стоит удалить в этом случае или вы получите смешные вещи при копировании части принтера. – RiaD

1

Вы можете создать один шаблон VARIADIC кетчуп все конструктор, который будет передавать аргументы базы и строительства принтера по мере необходимости, как этот

class Derived: ... { 
    template<typename... Args> 
    Derived(Args&&... args): Base(std::forward<Args>(args)...), Printer(*this) {} 
} 
Смежные вопросы