Существует ошибка в ваших рассуждениях. Конструктор всегда инициализирует все базовые классы и нестатические члены (технически виртуальная база инициализируется самым производным типом, а не любым конструктором других баз), поэтому базовый класс фактически инициализируется его конструктором по умолчанию, созданным компилятором (или создаваемые компилятором копии/перемещения конструкторы, если вы делаете копию или перемещение), который инициализирует все члены данных, используя их конструкторы по умолчанию (или копировать/перемещать). Вы можете позже назначить этих членов в конструкторах конкретного класса, но инициализация уже произошла к этому моменту.
Поскольку базовый класс владеет всеми членами данных, он фактически будет базовым классом, который инициализирует все члены данных при копировании или перемещении.Если вы пишете свою собственную копию или переместите конструктор в наиболее производном классе, вам нужно будет вызвать конструктор копирования/перемещения базового класса в списке инициализации или члены данных будут построены по умолчанию, и вы будете вынуждены используйте операцию копирования/перемещения после факта. Это часто неэффективно и в некоторых случаях может быть неправильным. (Например, я написал классы, которые могли бы быть построены по ходу движения, но не могли быть перенесены с переносом из-за проблем с отсечением; если бы у вас был такой класс в вашем Base_class как член данных, вы не могли бы реализовать семантику перемещения исключительно в Concrete_class.)
Если вы должны инициализировать все элементы данных из Concrete_class, я рекомендую вам предоставить защищенный конструктор в Base_class, который принимает всех членов данных по значению и перемещает их в свои собственные элементы данных, forwarding constructor в Traits_class (или наследовать конструктор базы, если вы используете компилятор с этой поддержкой). Это позволяет конкретному классу задавать значения для инициализации элементов данных, но позволяет базовому классу выполнять фактическую инициализацию. Он также позволяет конструкторам Base_class и Traits_class доступ к полностью инициализированным элементам данных (тогда как в противном случае они могут обращаться к членам данных только в состоянии инициализации по умолчанию).
Вот пример:
struct Base_class {
protected:
Base_class(string s) : s_(move(s)) { }
~Base_class() = default;
// Request copy/move, since we're declaring our own (protected) destructor:
Base_class(Base_class const &) = default;
Base_class(Base_class &&) = default;
Base_class &operator=(Base_class const &) = default;
Base_class &operator=(Base_class &&) = default;
string s_;
};
template<int>
struct Traits_class : Base_class {
protected:
template<typename... P>
Traits_class(P &&p)
: Base_class(forward<P>(p)...)
{ }
};
template<int I>
struct Concrete_class : Traits_class<I> {
Concrete_class(char c)
: Traits_class<I>(string(I, c))
{ }
};
В качестве побочного сведению, данные не обязательно должны быть в Base_class для Traits_class, чтобы иметь возможность доступа к нему. Вы можете обеспечить доступ через защищенную виртуальную функцию, если вы не против накладных расходов на вызов виртуальной функции, и если вам не нужен доступ к конструкторам или деструкторам (которые, как я полагаю, нет, поскольку в настоящее время данные члены не имеют своих окончательных значений до тех пор, пока не запустится конструктор Concrete_class). Или, чтобы избежать накладных расходов виртуального вызова, вы можете использовать шаблон Curiously Recurring Template, как упоминает @Yakk.
== РЕАКЦИЯ РЕДАКТИРУЙТЕ В ОРИГИНАЛЬНЫЙ ВОПРОС ==
Конструктор базового класса будет работать, прежде чем конструктор производного класса, поэтому, если данные хранятся в производном классе, он будет инициализирован в базе конструктор класса (и уже восстановлен в своем деструкторе). Вы можете придумать конструктор как экземпляр базового класса и превратить его в экземпляр производного класса (путем инициализации производной части класса и т. Д.), А в качестве специального случая - конструктор для класса без базовой классы превращают «ничего» (raw storage) в экземпляр класса.
Итак, когда работает ваш конструктор классов свойств, он еще не является конкретным производным классом. Следовательно, доступ к членам данных производного класса или иным образом с использованием класса как производного класса является незаконным. Язык даже обеспечивает это для виртуальных функций; если вы вызываете виртуальную функцию внутри конструктора базового класса или деструктора, он будет вызывать базовую версию функции. Пример:
#include <iostream>
using namespace std;
struct Base {
Base() { cout << "Base ctor\n"; v(); }
~Base() { v(); cout << "Base dtor\n"; }
protected:
virtual void v() const { cout << "Base::v\n"; }
};
struct Derived : Base {
Derived() { cout << "Derived ctor\n"; v(); }
~Derived() { v(); cout << "Derived dtor\n"; }
protected:
virtual void v() const { cout << "Derived::v\n"; }
};
int main() { Derived d; }
/* Output:
Base ctor
Base::v
Derived ctor
Derived::v
Derived::v
Derived dtor
Base::v
Base dtor
*/
«Я не называю базовый конструктор» - я думаю, что да. Если вы не укажете конструктора для вызова в списке инициализаторов конструктора производного класса, вы вызываете пустой базовый конструктор. Ваши рассуждения о том, почему данные в базе не являются твердыми - CRTP может предоставить доступ к данным в Trace_class для данных в производном классе. – Yakk
Спасибо Якку за ответ на мой пост. Я знаком с CRTP, но тогда мне пришлось бы хранить ссылку на производный класс, правильно? Или есть другой способ доступа к данным? – aaragon
'template struct CRTP_example {D * self() {return static_cast (this); D const * self() const {return static_cast (это); } int getx() const {return self() -> x; }}; struct Производные: CRTP_example {int x; Derived(): x (7) {}}; '- не связано использование ссылки. Вы захотите 'static_assert', что' CRTP_example 'также является базовым классом' D'. –
Yakk