2013-04-05 6 views
0

я следующая иерархия:дизайн и инициализация данных в производных классах

Base_class 
     | 
Traits_class 
     | 
Concrete_class 

Теперь дело в том, что данные, содержащиеся в Base_class (она должна быть там, потому что Traits_class должен иметь доступ к Traits_class - это шаблон класса, который имеет разную функциональность в зависимости от переданного параметра шаблона (поэтому я использую частичную специализацию шаблонов для разных классов). Наконец, на самом низком уровне Concrete_class также является шаблоном класса. Я создаю экземпляры Concrete_class только

Теперь возникает вопрос: я написал все конструкторы, деструктор, и я предоставил семантику перемещения в пределах Concrete_class. Это означает, что я не называю базовые конструкторы, но я инициализирую состояние непосредственно в производных классах. Может ли кто-нибудь указать, есть ли проблема с этим? Только деструктор объявлен в Base_class и объявлен как защищенный. Есть ли очевидный недостаток в этом дизайне?

Спасибо за понимание!

EDIT

Поэтому я пересмотрел дизайн следующий комментарий Yakk в на CRTP, и теперь у меня есть

Traits_class 
     | 
Concrete_class 

Я также переместил все данные в Concrete_class, и благодаря CRTP я могу имеют доступ к нему в Traits_class. Что-то странное произошло, хотя я не мог получить доступ к данным в Traits_class в конструкторе Traits_class. Я имею в виду, что я сделал доступ к нему, но мне казалось, что я получаю доступ к призрачным данным, потому что я инициализировал элементы в Traits_class (и даже напечатал в конструкторе Traits_class), но потом сразу после этого класс был пуст. Поэтому я действительно не понимаю, что произошло (я был const_casting Traits_class в Concrete_class, чтобы сделать это).

В конце я написал только статические функции-члены в Traits_class, чтобы инициализировать элементы Concrete_class. Думаю, я мог бы использовать защищенные функции-члены, чтобы делать то же самое (потому что я наследую от Traits_class), но я считаю, что это то же самое.

Если у вас есть дополнительные комментарии, сообщите мне. И еще раз спасибо за вашу мудрость C++.

аа

+0

«Я не называю базовый конструктор» - я думаю, что да. Если вы не укажете конструктора для вызова в списке инициализаторов конструктора производного класса, вы вызываете пустой базовый конструктор. Ваши рассуждения о том, почему данные в базе не являются твердыми - CRTP может предоставить доступ к данным в Trace_class для данных в производном классе. – Yakk

+0

Спасибо Якку за ответ на мой пост. Я знаком с CRTP, но тогда мне пришлось бы хранить ссылку на производный класс, правильно? Или есть другой способ доступа к данным? – aaragon

+2

'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

ответ

3

Существует ошибка в ваших рассуждениях. Конструктор всегда инициализирует все базовые классы и нестатические члены (технически виртуальная база инициализируется самым производным типом, а не любым конструктором других баз), поэтому базовый класс фактически инициализируется его конструктором по умолчанию, созданным компилятором (или создаваемые компилятором копии/перемещения конструкторы, если вы делаете копию или перемещение), который инициализирует все члены данных, используя их конструкторы по умолчанию (или копировать/перемещать). Вы можете позже назначить этих членов в конструкторах конкретного класса, но инициализация уже произошла к этому моменту.

Поскольку базовый класс владеет всеми членами данных, он фактически будет базовым классом, который инициализирует все члены данных при копировании или перемещении.Если вы пишете свою собственную копию или переместите конструктор в наиболее производном классе, вам нужно будет вызвать конструктор копирования/перемещения базового класса в списке инициализации или члены данных будут построены по умолчанию, и вы будете вынуждены используйте операцию копирования/перемещения после факта. Это часто неэффективно и в некоторых случаях может быть неправильным. (Например, я написал классы, которые могли бы быть построены по ходу движения, но не могли быть перенесены с переносом из-за проблем с отсечением; если бы у вас был такой класс в вашем 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 
*/ 
Смежные вопросы