Я думаю, що я розумію фактичні обмеження поліморфізму під час компіляції та поліморфізму під час виконання. Але в чому полягають концептуальні відмінності між явними інтерфейсами (поліморфізм під час виконання. Тобто віртуальні функції та покажчики / посилання) та неявними інтерфейсами (поліморфізм компіляції, тобто шаблони) .
Мої думки полягають у тому, що два об’єкти, які пропонують один і той же явний інтерфейс, повинні бути одного типу об’єктів (або мати спільного предка), тоді як два об'єкти, які пропонують той самий неявний інтерфейс, не повинні бути однотипними об'єктами, і, виключаючи неявні Інтерфейс, який вони обидва пропонують, може мати зовсім інший функціонал.
Будь-які думки з цього приводу?
І якщо два об’єкти пропонують один і той же неявний інтерфейс, то які причини (крім технічної вигоди від не потребує динамічної диспетчеризації з таблицею пошуку віртуальних функцій тощо) є тим, що ці об'єкти не успадковуються від базового об'єкта, який оголошує цей інтерфейс, таким чином що робить його явним інтерфейсом? Інший спосіб сказати це: чи можете ви надати мені випадок, коли два об’єкти, які пропонують однаковий неявний інтерфейс (і тому можуть використовуватися як типи до зразкового класу шаблонів), не повинні успадковувати базовий клас, який робить цей інтерфейс явним?
Деякі пов’язані публікації:
- https://stackoverflow.com/a/7264550/635125
- https://stackoverflow.com/a/7264689/635125
- https://stackoverflow.com/a/8009872/635125
Ось приклад, щоб зробити це питання більш конкретним:
Неявний інтерфейс:
class Class1
{
public:
void interfaceFunc();
void otherFunc1();
};
class Class2
{
public:
void interfaceFunc();
void otherFunc2();
};
template <typename T>
class UseClass
{
public:
void run(T & obj)
{
obj.interfaceFunc();
}
};
Явний інтерфейс:
class InterfaceClass
{
public:
virtual void interfaceFunc() = 0;
};
class Class1 : public InterfaceClass
{
public:
virtual void interfaceFunc();
void otherFunc1();
};
class Class2 : public InterfaceClass
{
public:
virtual void interfaceFunc();
void otherFunc2();
};
class UseClass
{
public:
void run(InterfaceClass & obj)
{
obj.interfaceFunc();
}
};
Ще більш поглиблений, конкретний приклад:
Деякі проблеми C ++ можна вирішити будь-яким:
- шаблонний клас, тип шаблону якого дає неявний інтерфейс
- не шаблонований клас, який приймає покажчик базового класу, який забезпечує явний інтерфейс
Код, який не змінюється:
class CoolClass
{
public:
virtual void doSomethingCool() = 0;
virtual void worthless() = 0;
};
class CoolA : public CoolClass
{
public:
virtual void doSomethingCool()
{ /* Do cool stuff that an A would do */ }
virtual void worthless()
{ /* Worthless, but must be implemented */ }
};
class CoolB : public CoolClass
{
public:
virtual void doSomethingCool()
{ /* Do cool stuff that a B would do */ }
virtual void worthless()
{ /* Worthless, but must be implemented */ }
};
Випадок 1 . Неспланований клас, який приймає покажчик базового класу, що забезпечує явний інтерфейс:
class CoolClassUser
{
public:
void useCoolClass(CoolClass * coolClass)
{ coolClass.doSomethingCool(); }
};
int main()
{
CoolA * c1 = new CoolClass;
CoolB * c2 = new CoolClass;
CoolClassUser user;
user.useCoolClass(c1);
user.useCoolClass(c2);
return 0;
}
Випадок 2 . Шаблон шаблону, тип шаблону якого надає неявний інтерфейс:
template <typename T>
class CoolClassUser
{
public:
void useCoolClass(T * coolClass)
{ coolClass->doSomethingCool(); }
};
int main()
{
CoolA * c1 = new CoolClass;
CoolB * c2 = new CoolClass;
CoolClassUser<CoolClass> user;
user.useCoolClass(c1);
user.useCoolClass(c2);
return 0;
}
Випадок 3 . Шаблон шаблону, тип шаблону якого надає неявний інтерфейс (цього разу не випливає з CoolClass
:
class RandomClass
{
public:
void doSomethingCool()
{ /* Do cool stuff that a RandomClass would do */ }
// I don't have to implement worthless()! Na na na na na!
}
template <typename T>
class CoolClassUser
{
public:
void useCoolClass(T * coolClass)
{ coolClass->doSomethingCool(); }
};
int main()
{
RandomClass * c1 = new RandomClass;
RandomClass * c2 = new RandomClass;
CoolClassUser<RandomClass> user;
user.useCoolClass(c1);
user.useCoolClass(c2);
return 0;
}
Випадок 1 вимагає, щоб об'єкт, що передається, useCoolClass()
був дитиною CoolClass
(і реалізовувався worthless()
). Випадки 2 і 3, з іншого боку, прийматимуть будь-який клас, який має doSomethingCool()
функцію.
Якби користувачі коду завжди були тонкокласифіковані CoolClass
, тоді Case 1 має інтуїтивний сенс, тому що CoolClassUser
завжди очікував би впровадження а CoolClass
. Але припустимо, що цей код буде частиною рамки API, тому я не можу передбачити, чи користувачі захочуть підклас CoolClass
або згорнути власний клас, який має doSomethingCool()
функцію.