У меня есть ряд структур, которые получены из одной и той же базы для повторного использования кода, но я не хочу какой-либо формы полиморфизма.
struct B {
int field;
void doStuff() {}
bool operator==(const B& b) {
return field == b.field;
}
};
struct D1 : public B {
D1(int field) : B{field} {}
};
struct D2 : public B {
D2(int field) : B{field} {}
};
Структуры D1
а также D2
(и более похожие структуры) происходят из B
использовать общие поля и методы, чтобы мне не нужно было дублировать эти поля и методы в каждом из производных классов.
Struct B
никогда не создается; Я использую только случаи D1
а также D2
, Более того, D1
а также D2
не должны взаимодействовать друг с другом вообще. По сути, я не хочу никакого полиморфного поведения: D1
а также D2
для всех целей должен действовать как не связанные структуры.
Я хотел бы любой D1
сравнивать с другими D1
за равенство, и любой D2
сравнивать с другими D2
s для равенства. поскольку D1
а также D2
не содержит полей, было бы целесообразно определить оператор равенства в структуре B
,
Однако (как и ожидалось) я получаю следующее взаимодействие между D1
а также D2
:
int main() {
D1 d1a(1);
D1 d1b(1);
D2 d2(1);
assert(d1a == d1b); // good
assert(d1a == d2); // oh no, it compiles!
}
Я не хочу иметь возможность сравнивать D1
с D2
объекты, потому что для всех целей они должны действовать так, как будто они не связаны.
Как я могу сделать последнее утверждение ошибкой компиляции без дублирования кода? Определение оператора равенства отдельно для D1
а также D2
(и все другие подобные структуры) будет означать дублирование кода, поэтому я хотел бы избежать этого, если это возможно.
Ты можешь использовать CRTP определить operator ==
только по ссылке на базовый класс конечного типа:
template<typename T>
struct B {
int field;
void doStuff() {}
bool operator==(const B<T>& b) {
return field == b.field;
}
};
struct D1 : public B<D1> {
D1(int field) : B{field} {}
};
struct D2 : public B<D2> {
D2(int field) : B{field} {}
};
Это вызывает первое assert
компилировать и второй отклонить.
«Структуры D1 и D2 (и более похожие структуры) являются производными от B и используют общие поля и методы»
Тогда сделай B
private
Базовый класс. Очевидно, что D1
а также D2
не должен разделять свой оператор равенства, так как два оператора принимают разные аргументы. Конечно, вы можете поделиться частью реализации как bool B::equal(B const&) const
, поскольку это не будет доступно для внешних пользователей.
Вместо определения вашего оператора равенства как части базового класса, вам обычно нужны две функции в производных классах:
struct B {
int field;
void doStuff() {}
};
struct D1 : public B {
D1(int field) : B{field} {}
bool operator==(const D1& d) {
return field == d.field;
}
};
struct D2 : public B {
D2(int field) : B{field} {}
bool operator==(const D2& d) {
return field == d.field;
}
};
Или, как правило, вы можете сделать их свободными функциями:
bool operator==(const D1 &lhs, const D1 &rhs)
{
return lhs.field == rhs.field;
}
bool operator==(const D2 &lhs, const D2 &rhs)
{
return lhs.field == rhs.field;
}
Замечания: Если field
не был публичным членом, вам нужно объявить версию бесплатной функции как friend
,
Хорошо, так что, возможно, у вас есть D3
через D99
Кроме того, некоторые из них являются косвенными потомками B
, Вам нужно будет использовать шаблоны:
template <class T>
bool operator==(const T &lhs, const T &rhs)
{
return lhs.field == rhs.field;
}
Большой! Но это хватается все, что плохо для несвязанных типов, которые не должен быть сопоставимым. Поэтому нам нужны ограничения.
Вот тривиальная реализация без дублирования кода (т. Е. Для произвольного числа производных типов):
template <class T, class = std::enable_if<std::is_base_of<B,T>()
&& !std::is_same<B, std::remove_cv_t<std::remove_reference_t<T>>>()>>
bool operator==(const T &lhs, const T &rhs)
{
return lhs.field == rhs.field;
}
enable_if
сначала проверяет, что T
наследуется от B
затем гарантирует, что это не B
, Вы заявили в своем вопросе, что B
в основном абстрактный тип и никогда не реализуется напрямую, но это тест во время компиляции, так почему бы не быть параноиком?
Как вы позже отметили в комментариях, не все D#
получены непосредственно из B
, Это все еще будет работать.
Учитывая следующее:
D1 d1(1);
D2 d2(2);
d1 == d2;
Компилятор должен найти оператор сравнения, будь то свободная функция или член D1
(не D2
). К счастью, вы определили один в классе B
, В третьей строке выше можно эквивалентно указать:
d1.operator==(d2)
operator==
Однако является частью B
так что мы в основном звоним B::operator==(const B &)
, Почему это работает, когда D2
не является B
? Специалист по языку уточнил бы, является ли это технически зависимым от аргументов поиском (ADL) или разрешением перегрузки, но эффект таков, что D2
молча приведен к B
как часть вызова функции, что эквивалентно приведенному выше:
d1.operator==(static_cast<B>(d2));
Это происходит потому, что лучшая функция сравнения не может быть найдена. Поскольку альтернативы нет, компилятор выбирает B::operator==(const B &)
и делает бросок.
Вы можете удалить оператор равенства из исходного определения структуры и заменить его на шаблон функции, принимающий два идентичных типа параметров:
template <class T> bool operator == (const T& lhs, const T& rhs)
{
return lhs.field == rhs.field;
}
Обратите внимание, что эта функция несколько «жадная», лучше всего поместить ее в пространство имен (вместе со структурами, чтобы включить ADL) или дополнительно ограничить такие типы:
#include <type_traits>
template <class T, std::enable_if_t<std::is_base_of_v<B, T>, int> = 0>
bool operator == (const T& lhs, const T& rhs)
{
return lhs.field == rhs.field;
}
(Обратите внимание, что std::is_base_of_v
требует C ++ 17, но подробный аналог существует с C ++ 11).
Как последний твик, для предотвращения такой явной реализации:
operator == <B>(d1a, d2); // ultra-weird usage scenario, but compiles!
или (как указал @Aconcagua в комментариях) вывод типа со ссылками базового класса на производные структуры,
B& b1 = d1a;
B& b2 = d2;
assert(b1 == b2); // Compiles, but see below.
Вы также можете добавить
template <> bool operator == <B>(const B&, const B&) = delete;