Форум программистов, компьютерный форум CyberForum.ru

Как инициализировать член раньше предка - C++

Восстановить пароль Регистрация
 
Nick Alte
Эксперт С++
1590 / 982 / 115
Регистрация: 27.09.2009
Сообщений: 1,897
Завершенные тесты: 1
12.01.2012, 19:30     Как инициализировать член раньше предка #1
Изредка, но может встретиться в жизни такая ситуация, когда надо инициализировать один из членов класса раньше предка. Обычно в том случае, если при инициализации предок ссылается на данные этого члена. Приведу пример:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Foundation { // Фундамент
public:
    Foundation(float Width, float Height);  // Выкопать яму нужных размеров и залить бетоном
private:
    // Нельзя копировать и присваивать
    Foundation(const Foundation&);
    Foundation& operator = (const Foundation&);
};
 
class FloorPlan {  // Чертежи дома
public:
    FloorPlan(const string& name);  // По названию модели дома
    FloorPlan(const FloorPlan&);    // Копировать в принципе можно, но дорого...
    float Width() const;    // Ширина и высота основания
    float Height() const;
private:
    FloorPlan& operator = (const FloorPlan&);
};
 
class Floor { // Этаж
// ...
};
 
class Building: public Foundation { // Здание
private:
    FloorPlan plan;
    vector<Floor> floors;
public:
    Building(const string& name): Foundation(plan.Width(), plan.Height()), plan(name) {ConstructFloors();} // УПС!
    static void ConstructFloors(vector<Floor>& floors, const FloorPlan& plan);
private:
    // Нельзя копировать и присваивать
    Building(const Building&);
    Building& operator = (const Building&);
};
Класс Building наследует от Foundation и содержит чертёж как член. Для создания фундамента надо знать размеры, которые может предоставить чертёж. Но вот досада-то: фундамент является предком и инициализируется раньше всех членов. Так что мы не можем использовать ещё не инициализированный plan, чтобы узнать размеры Foundation. Как быть? Во-первых, можно наследовать Building от FloorPlan и Foundation:
C++
1
2
3
4
5
6
class Building: private FloorPlan, public Foundation {
private:
    vector<Floor> floors;
public:
    Building(const string& name): FloorPlan(name), Foundation(Width(), Height()) {ConstructFloors(floors, *this);}
};
Недостатки метода очевидны: мало того, что мы нарушаем идеологическую чистоту, мы ещё и заливаем в Building интерфейс FloorPlan, что может привести к замусориванию и неожиданным последствиям.

Второй подход проще и очевиднее: надо сначала создать отдельный объект FloorPlan, использовать его для создания Foundation и перекинуть в Building:
C++
1
2
3
4
5
6
7
8
class Building: public Foundation {
private:
    FloorPlan plan;
    vector<Floor> floors;
public:
    Building(const FloorPlan &p): Foundation(p.Width(), p.Height()), plan(p) {ConstructFloors(floors, plan);}
    // Теперь вместо Building d("дача"); надо писать Building d(FloorPlan("дача"));
};
Плохо то, что приходится копировать FloorPlan: это может быть дорогостоящая операция (очереди в мэрии за разрешениями, взятки клеркам, медленный ксерокс). Можно передавать не сам FloorPlan, а указатель на динамически создаваемый объект с передачей владения. Возможности нового стандарта C++11, уже реализованные в последних версиях компиляторов Visual Studio 2010 и GCC позволяет максимально чётко выразить передачу владения и защититься от неприятных последствий при помощи unique_ptr:
C++
1
2
3
4
5
6
7
8
9
class Building: public Foundation {
private:
    std::unique_ptr<const FloorPlan> pplan; // Автоматически уничтожается при уничтожении Building.
    vector<Floor> floors;
public:
    Building(std::unique_ptr<const FloorPlan> p): Foundation(p->Width(), p->Height()), pplan(std::move(p)) {ConstructFloors(floors, *pplan);}
    // Теперь конструирование выглядит ещё веселее:
    // Building d(std::unique_ptr<FloorPlan>(new FloorPlan("дача")));
};
Теперь мы избавились от затратного копирования чертежей, однако неужели нельзя сделать лучше? Разумеется, можно. Тот же unique_ptr основывается на концепции уникального объекта, содержимое которого нельзя копировать, но можно перемещать. Так почему бы не сделать FloorPlan перемещаемым?
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class FloorPlan {
public:
    FloorPlan(const string& name); 
    FloorPlan(FloorPlan&& source);  // Перекидываем всё содержимое в this и помечаем source как "пустой"
    float Width() const;
    float Height() const;
};
 
class Building: public Foundation {
private:
    FloorPlan plan;
    vector<Floor> floors;
public:
    Building(FloorPlan &&p): Foundation(p.Width(), p.Height()), plan(std::move(p)) {ConstructFloors(floors, plan);}
    // Мы снова вернулись к Building d(FloorPlan("дача"));
};
Но и этого мало! Здания должны конструироваться с удобством! И при помощи другой особенности С++11, делегации конструкторов, мы можем это сделать:
C++
1
2
3
4
5
6
7
8
9
10
11
class Building: public Foundation {
private:
    FloorPlan plan;
    vector<Floor> floors;
    // Теперь мы спрячем этот конструктор от народа
    Building(FloorPlan &&p): Foundation(p.Width(), p.Height()), plan(p) {ConstructFloors(floors, plan);}
public:
    Building(const string& name): Building(FloorPlan(name)) {} // Просто создаём временный объект FloorPlan и перекидываем создание скрытому конструктору
    // Мы снова вернулись к старому доброму Building d("дача");
private:
};
Добавлю ложку дёгтя в бочку мёда: Visual Studio 2010 делегировать конструкторы не умеет. Поиграться таким фокусом можно пока только в GCC начиная с версии 4.7.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
12.01.2012, 19:30     Как инициализировать член раньше предка
Посмотрите здесь:

Как инициализировать структуру C++
C++ Одномерные массивы. Найти максимальный член в массиве, начиная со второго член
В последовательности а1,...,a30 поменять местами наибольший член и член с номером m. C++
Можно ли инициализировать static член класса функцией? C++
C++ Как объявить указатель на массив через typedef и как инициализировать такой тип
Как инициализировать массив в классе C++
C++ Поменять местами наибольший член последовательности и член с номером m
C++ Как инициализировать массивы?

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
lemegeton
 Аватар для lemegeton
2910 / 1339 / 133
Регистрация: 29.11.2010
Сообщений: 2,720
13.01.2012, 08:27     Как инициализировать член раньше предка #2
Плохой пример. В данной системе классов больше подходит включение, а не наследование.

Например, Здание не логично наследовать от Фундамента или ПланаЭтажа, поскольку здание не ведет себя как фундамент или как план этажа. Тут откровенно напрашивается включение. FloorPlan включен во Floor (1-1), Floor в Building (*-1), Foundation в Building (1-1).
Yandex
Объявления
13.01.2012, 08:27     Как инициализировать член раньше предка
Ответ Создать тему
Опции темы

Текущее время: 06:09. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru