Допустим, у нас есть два класса, A
а также B
, При использовании композиции для моделирования «имеет» или же «Является реализованными в-плана, из» отношения (например, B
имеет A
), один из недостатков против наследования заключается в том, что B
не содержит публичную функциональность A
что для этого требуется. Для того, чтобы получить доступ к A
Публичные функции, необходимо обеспечить функции пересылки (в отличие от наследования, где B
унаследует все A
публичные функции).
Чтобы дать более конкретный пример, скажем, у нас есть Person
который имеет ContactInfo
:
using namespace std;
class ContactInfo
{
public:
ContactInfo();
void updateAddress(string address);
void updatePhone(string phone);
void updateEmail(string email);
private:
string address;
string phone;
string email;
};
class Person
{
public:
Person();
// Forwarding functions:
void updateAddress(string address){contactInfo.updateAddress(address)};
void updatePhone(string phone){contactInfo.updatePhone(phone)};
void updateEmail(string email){contactInfo.updateEmail(email)};
private:
ContactInfo contactInfo;
};
Игнорируя любые недостатки в дизайне (это просто надуманный пример, чтобы продемонстрировать мой вопрос ниже), мне пришлось утомительно копировать точные сигнатуры функций из ContactInfo
в Person
, В более сложном примере может быть много таких функций и многоуровневых составных классов, что приводит к значительному дублированию кода со всеми обычными проблемами обслуживания и подверженности ошибкам и т. Д.
Тем не менее, это рекомендуемая практика для моделирования «имеет» или же «Является реализованными в-плана, из» согласно источникам, таким как Пункт 38 Эффективного C ++ Мейерса, и Пункт 24 Исключительного C ++ Саттера (ссылка на сайт).
Исследуя это, я наткнулся на эта статья в Википедии который обсуждает ту же тему. В нижней части статьи, это предполагает следующее:
Одним из недостатков использования композиции вместо наследования является то, что все
методы, предоставляемые составными классами, должны быть
реализовано в производном классе, даже если они только переадресация
методы. […] Этого недостатка можно избежать, используя черты.
Я довольно плохо знаком с понятием черт и, учитывая все прочитанное, мне трудно соотноситься с приведенным выше утверждением. Поэтому мой вопрос: Как можно использовать черты, чтобы избежать перенаправления функций с композицией? Ответ основан на моем примере (Person
а также ContactInfo
) было бы идеально.
РЕДАКТИРОВАТЬ: Просто чтобы прояснить, в ответ на некоторые ответы я знаю о частном наследовании как об альтернативе композиции для моделирования «Является реализованными в-плана, из». Мой вопрос не об этом, а конкретно о значении заявления Википедии, касающегося черт характера. Я не прошу альтернативы композиции. Я выделил свой вопрос, чтобы прояснить, что это то, что я спрашиваю.
Прежде всего, я должен отметить, что признаки — это разные вещи в C ++ / STL и таких языках, как PHP, Lasso и т. Д. Похоже, что статья из Википедии ссылается на PHP-подобные признаки, потому что признаки C ++ / STL не предназначены для полиморфного повторного использования (мы говоря о повторном использовании кода с полиморфным поведением, верно?). Они предназначены для упрощения объявления шаблонных классов.
Черты используются в некоторых языках, которые не поддерживают множественное наследование (PHP, Lasso и т. Д.). Черты позволяют «эмулировать» множественное наследование (но множественное наследование и черты не совсем совпадают).
В отличие от C ++ не поддерживает PHP-черты, но поддерживает множественное наследование. Так что, если говорить о C ++, то решение, похожее на черту, будет примерно таким:
class VisibleTrait
{
public:
virtual void draw();
};
class SolidTrait
{
public:
virtual void collide(Object objects[]);
};
class MovableTrait
{
public:
virtual void update();
};// A player is visible, movable, and solid
class Player : public VisibleTrait, MovableTrait, SolidTrait
{
};
// Smoke is visible and movable but not solid
class Smoke : public VisibleTrait, MovableTrait
{
};
// A hause is visible and solid but not movable
class House : public VisibleTrait, SolidTrait
{
};
Итак, чтобы ответить на ваш вопрос
Как можно использовать черты, чтобы избежать пересылки функций с
состав?
1) Черты не избегают пересылки функций с композицией, потому что черты работают независимо от композиции. (Статья из Википедии немного вводит в заблуждение относительно отношений между чертами и составом)
2) PHP / Lasso-подобные черты могут быть частично эмулированы в C ++ с множественным наследованием.
Может быть, вы можете попробовать частное наследование:
class Person : private ContactInfo
{
public:
Person() { }
using ContactInfo::updateAddress;
using ContactInfo::updatePhone;
using ContactInfo::updateEmail;
};
int main()
{
Person person;
person.updateAddress("hi");
return 0;
}
Хотя вы можете не упускать из виду предостережения, перечисленные в этом Часто задаваемые вопросы:
Есть также несколько различий:
- Вариант простой композиции необходим, если вы хотите содержать несколько двигателей на автомобиль
- Вариант частного наследования может привести к ненужному множественному наследованию
- Вариант частного наследования позволяет членам Car преобразовать автомобиль * в двигатель *
- Вариант частного наследования позволяет получить доступ к защищенным членам базового класса.
- Вариант частного наследования позволяет Car переопределять виртуальные функции Engine
- Вариант приватного наследования упрощает (20 символов по сравнению с 28 символами) метод Car ()
это просто вызывает метод start () Engine
В остальном пример представленной композиции кажется идентичным вашему. Нажатие на ссылку черты в статье в Википедии не предоставило никаких статей C ++, и ссылка в ссылках, кажется, о type traits
, Я не мог найти как type traits
имеет какое-либо отношение к вашему сценарию.
В статье рассказывается о наследовании с Интерфейс,
так на самом деле это говорит о том, что объект должен уважать некоторые подписи.
черты типа можно использовать для проверки правильности подписи и отправки в соответствующую функцию
Например, некоторые алгоритмы STL ждут тип Итератор,
но эти итераторы не наследуют от class Iterator
но должен предоставить какой-то контракт (operator ++()
, operator !=(rhs)
, operator*()
).
с примером статьи:
И код:
#if 1
// simple type traits which tells if class has method update_position
template <typename T> struct can_move;
// Hardcode the values
template <> struct can_move<Player> { static const bool value = true; };
template <> struct can_move<Building> { static const bool value = false; };
#else
// or even better, but need a has_update_position. (see how to check if member exist in a class)
template <typename T> struct can_move{ static const bool value = has_update_position<T>::value };
#endif
template <typename T, bool> struct position_updater;
// specialization for object which can move
template <typename T> struct position_updater<T, true>
{
static void update(T& object) { object.update_position(); }
};
// specialization for object which can NOT move
template <typename T> struct position_updater<T, false>
{
static void update(T& object) { /* Do nothing, it can not move */ }
};template <typename T>
void update_position(T& object)
{
// statically dispatch to the correct method
// No need of interface or inheritance
position_updater<T, can_move<T>::value>::update(object);
}
AFAIK класс черт что-то вроде следующего:
#include <iostream>
using namespace std;
class ContactInfo
{
public:
void updateAddress() { cout << "update address"; };
void updatePhone() {};
void updateEmail() {};
};
template<class T> class TraitClass
{
public:
private:
T obj;
};
template<> class TraitClass<ContactInfo>
{
public:
void updateAddress() {obj.updateAddress();};
void updatePhone() {obj.updatePhone();};
void updateEmail() {obj.updateEmail();};
private:
ContactInfo obj;
};
class Person
{
public:
void updateAddress() {obj.updateAddress();};
void updatePhone() {obj.updatePhone();};
void updateEmail() {obj.updateEmail();};
private:
TraitClass<ContactInfo> obj;
};
int main() {
Person myPerson;
myPerson.updateAddress();
return 0;
}
То есть: шаблонный класс во время компиляции, где вы можете реализовать (и / или специализировать его) ваши методы делегата, чтобы пересылать все, что вам нужно, без (по какой бы то ни было причине) повторения наследования.