Хорошие варианты:
Сделать у Autogearbox
геттер в духе bool IsBroken() const;
, который Car
сам бы звал время от времени.
Возвращать из каких-то методов Autogearbox
результат - сломано или не сломано.
Передавать в какие-то методы Autogearbox
колбек (std::function<void()> onBreak
), и звать его при поломке. Еще варианты - отдавать указатель на интерфейс, от которого бы наследовался Car
, и в котором была бы функция virtual void onAutogearboxBreak() = 0;
.
Кидать исключение при поломке. Учитывайте, что исключения медленные, и это подходит, если поломки редкие.
Плохие варианты:
Передавать указатель на Car
. Потому что тогда получается, то Autogearbox
и Car
могут использоваться только вместе, и становится не очень понятно, зачем иметь два класса, и почему не сложить все в один.
В этом смысле выглядит лучше передавать колбек, или указатель на интерфейс, от которого наследовать Car
.
Передавать колбек/указатель, и сохранять его в поле Autogearbox
, вместо того, чтобы использовать сразу.
Если Car
переместят, то указатель на него протухнет. (Если там колбек вместо указателя, то внутри лямбды, которую вы будете туда передавать, скорее всего тоже окажется указатель на Car
.)
Этого можно избежать, сделав Car
некопируемым и неперемещаемым, но это не лучший ход.
Хитроумные варианты:
Наследовать Car
от Autogearbox
. В последнем тогда была бы чисто виртуальная функция, которую тот бы звал при поломке.
Вариации:
Тоже наследовать, но с CRTP. Немного сэкономите на вызове виртуальных функций.
Вытащить метод поломки в интерфейс, наследоваться от него виртуально и в Autogearbox
и в Car
, и в последнем переопределять метод. Тогда несколько компонентов Car
смогут дергать один и тот же метод.
Если нужно несколько Autogearbox
на один Car
, то такие варианты:
Наследоваться от Autogearbox
, но сделать его шаблоном, чтобы можно было давать ему имя: struct Car : Autogearbox<"gearbox_a">, Autogearbox<"gearbox_b">
(как передавать строки в шаблонные параметры).
Сделать Autogearbox
полем Car
, и хранить в нем смещение до Car
. Смещение брать из offsetof(Car, my_gearbox)
. Использовать изнутри Autogearbox
примерно так: (Car *)((char *)this - смещение)
. (Есть подозрение, что это UB, но на практике почти наверняка не сломается.)
Тогда желательно сделать эти поля приватными, чтобы никто случайно не вынул их Car
. Чтобы не привязываться к типу Car
, можно сделать Autogearbox
шаблоном и передавать Car
как шаблонный аргумент.
То же самое, но сэкономить на хранении смещения, прокинув его через stateful metaprogramming:
запустить на gcc.godbolt.org
#include <cstddef>
#include <iostream>
#include <type_traits>
#if defined(__GNUC__) && !defined(__clang__)
#define NON_TEMPLATE_FRIEND(...) \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wnon-template-friend\"") \
__VA_ARGS__ \
_Pragma("GCC diagnostic pop")
#else
#define NON_TEMPLATE_FRIEND(...) __VA_ARGS__
#endif
#define CAT(x, y) CAT_(x, y)
#define CAT_(x, y) x##y
// Macros to determine the type of the enclosing class.
namespace SelfType
{
template <typename Key>
struct Read
{
NON_TEMPLATE_FRIEND(
friend constexpr auto _adl_SelfType(Read<Key>);
)
};
template <typename Key, typename Value>
struct Write
{
friend constexpr auto _adl_SelfType(Read<Key>) {return Value{};}
};
constexpr void _adl_SelfType() {} // Dummy ADL target.
template <typename T>
using Type = std::remove_pointer_t<decltype(_adl_SelfType(Read<T>{}))>;
}
// Creates a typedef named `self_`, pointing to the enclosing class.
// You can have several of those in a class, if the names are different.
#define SELF_TYPE(self_) \
struct CAT(_adl_SelfTypeTag, self_) {}; \
auto _adl_SelfTypeHelper(CAT(_adl_SelfTypeTag, self_)) \
-> decltype(void(::SelfType::Write<CAT(_adl_SelfTypeTag, self_), decltype(this)>{})); \
using self_ = ::SelfType::Type<CAT(_adl_SelfTypeTag, self_)>;
// Stateful metaprogramming to store the offset.
namespace OwnerOffset
{
template <typename Key>
struct Read
{
NON_TEMPLATE_FRIEND(
friend constexpr auto _adl_OwnerOffset(Read<Key>);
)
};
template <typename Key, std::ptrdiff_t Offset>
struct Write
{
friend constexpr auto _adl_OwnerOffset(Read<Key>) {return Offset;}
};
constexpr void _adl_OwnerOffset() {} // Dummy ADL target.
template <typename T>
constexpr std::ptrdiff_t Value = _adl_OwnerOffset(Read<T>{});
}
template <typename Owner, typename Tag>
struct KnowsOwner
{
[[nodiscard]] Owner &owner()
{
return *(Owner *)((char *)this - OwnerOffset::Value<Tag>);
}
[[nodiscard]] const Owner &owner() const
{
return const_cast<KnowsOwner *>(this)->owner();
}
};
#define MAKE_VAR(name_, .../*type*/) \
IMPL_MAKE_VAR(__COUNTER__, name_, __VA_ARGS__)
#define IMPL_MAKE_VAR(counter_, name_, .../*type*/) \
/* Determine self type. */\
SELF_TYPE(CAT(_self_,counter_))\
/* Make a helper struct. */\
struct CAT(_tag_,counter_) {};\
/* Make our variable. */\
__VA_ARGS__<CAT(_self_,counter_), CAT(_tag_,counter_)> name_{}; \
/* Store the offset. */\
static void CAT(_offset_writer_,counter_)() {(void)::OwnerOffset::Write<CAT(_tag_,counter_), offsetof(CAT(_self_,counter_), name_)>{};}\
/* Force instantiate the previous function. */\
static constexpr std::integral_constant<void (*)(), &CAT(_offset_writer_,counter_)> CAT(_offset_writer2_,counter_) {};
// ---
template <typename Owner, typename Tag>
struct A : KnowsOwner<Owner, Tag>
{
int unused = 0;
void foo()
{
std::cout << this->owner().x << '\n';
}
};
struct B
{
int x = 42;
MAKE_VAR(f, A)
MAKE_VAR(g, A)
};
int main()
{
B b;
b.f.foo(); // 42
b.g.foo(); // 42
}