Я хочу понять, существует ли какой-то единый способ лечения циклических зависимостей?
Проблема: по той или иной причине в проекте возникают перекрестные ссылки между типами. Такие ситуации иногда случаются, несмотря на предварительное планирование архитектуры. Такие ссылки не всегда являются ошибками.
Самым неприятным в этой ситуации является то, что в большинстве случаев компилятор выдает тонну уведомлений. При этом, уведомления указывают в неверные места, и только опыт помогает более или менее быстро понять, что причиной является циклическая зависимость.
К тому же, многие постоянно забывают, что #pragma once
защищает от множественного включения, а не от циклических зависимостей.
К примеру, есть сцена с боем, есть бой, бой содержит ссылку на родительскую сцену:
// BattleScene.hpp
class BattleScene
{
private:
Battle battle;
};
// Battle.hpp
class Battle
{
private:
BattleScene &parrent;
};
Каков общий способ решения такого вопроса?
Для того, чтобы компилятор не ругался - достаточно использовать предварительное объявление:
class BattleScene;
class Battle
{
private:
BattleScene &parent;
};
Замечу, что объявленный, но ещё не определенный класс нельзя использовать по значению, только по ссылке или по указателю.
Однако, с циклическими ссылками есть и вторая проблема: циклические структуры данных невозможно тривиально скопировать или перенести.
Так, если вы попытаетесь сделать вот так:
BattleScene copy = someOtherScene;
то окажется, что copy.battle.parent
указывает не на copy
, а на someOtherScene
! Поэтому не забывайте запрещать копирование и перемещение!
class BattleScene;
class Battle
{
private:
BattleScene &parent;
Battle(const Battle&) = delete;
Battle(Battle&&) = delete;
Battle& operator=(const Battle&) = delete;
Battle& operator=(Battle&&) = delete;
};
Ещё одна проблема с циклическими структурами - возможная потеря константности. Так, если у вас есть переменная const BattleScene scene
, то вы можете взять scene.battle.parent
- и получить неконстантную ссылку на scene
!
Кроме всего этого, лишние ссылки банально неэффективны по памяти.
Поэтому предлагаю вам рассмотреть альтернативу - передачу родительского объекта параметром в методы дочернего:
class BattleScene;
class Battle
{
public:
void Foo(BattleScene& parent);
};
class BattleScene
{
private:
Battle battle;
public:
void Bar()
{
battle.Foo(*this);
}
};
Если вам требуется куда-то передавать ссылку на Battle отдельно - можно сделать обёртку:
class BattleScene;
class Battle
{
public:
void Foo(BattleScene& parent);
};
class BattleRef
{
BattleScene& scene;
Battle& battle;
public:
BattleRef(BattleScene& scene, Battle& battle) : scene(scene), battle(battle) { }
void Foo()
{
battle.Foo(scene);
}
};
// BattleScene.hpp
#include "Battle.hpp"
// Battle.hpp
class BattleScene;
class Battle
{
private:
BattleScene &parrent;
};
Универсальный способ лечения циклических зависимостей можно создать, но только сначала нужно понять порядок объявлений и зависимостей между ними.
Самый первый простой элемент это предварительное объявление типа. Потом объявление класса с их элементами и методами. Только без объявления методов класса внутри него! Это может привести к невозможности компиляции. Дальше эти самые инлайн функции. И потом определения простых статических методов.
Допустим есть два класса, которе содержат ссылку друг на друга и методы с аргументом типа другого класса. Вот пример как всё нужно правильно объявлять :
A.hpp :
# pragma once
// предварительное объявление класса B
class B ;
class A {
public :
A ( B & ) ;
B & theB();
// аргументом по значению тоже можно
void Argument_B_by_value ( B );
private :
B & b ;
} ;
B.hpp :
# pragma once
// предварительное объявление класса A
class A ;
class B {
public :
B ( A & ) ;
A & theA();
// аргументом по значению тоже можно
void Argument_A_by_value ( A );
private :
A & a ;
} ;
Ai.hpp :
// порядок не важен
# include "A.hpp"
# include "B.hpp"
inline B & A :: theB ( ) {
return b ; }
inline void A :: Argument_B_by_value ( B ) {
}
Bi.hpp :
// порядок не важен
# include "B.hpp"
# include "A.hpp"
inline A & B :: theA ( ) {
return a ; }
inline void B :: Argument_A_by_value ( A ) {
}
A.cpp :
// порядок не важен
# include "A.hpp"
# include "B.hpp"
// порядок не важен
# include "Ai.hpp"
# include "Bi.hpp"
A :: A ( B & x ) : b { x } { }
B.cpp :
// порядок не важен
# include "B.hpp"
# include "A.hpp"
// порядок не важен
# include "Bi.hpp"
# include "Ai.hpp"
B :: B ( A & x ) : a { x } { }
Получается три уровня с разными именами файлов :
Предварительное объявление других типов. Определение класса.
Инлайн методы.
Статические методы.
Второй уровень включает хедеры первого уровня. Третий уровень включает хедеры первого и второго уровня.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
CodeBlocks на Ubuntu 1804 не форматирует фигурные скобки как в Windows версии
Подскажите пожалуйста, как сделать что бы время было по центру border-right?
Есть код, как увеличить время возврата в первоначальное положение, те