Я думаю, что понимаю реальные ограничения полиморфизма во время компиляции и полиморфизма во время выполнения. Но каковы концептуальные различия между явными интерфейсами (полиморфизм во время выполнения. Т.е. виртуальные функции и указатели / ссылки) и неявными интерфейсами (полиморфизм во время компиляции. Т.е. шаблоны) .
Я думаю, что два объекта, которые предлагают один и тот же явный интерфейс, должны быть объектом одного и того же типа (или иметь общего предка), в то время как два объекта, которые предлагают один и тот же неявный интерфейс, не обязательно должны быть объектом одного и того же типа, и, исключая неявный Интерфейс, который они оба предлагают, может иметь совершенно разную функциональность.
Есть мысли по этому поводу?
И если два объекта предлагают один и тот же неявный интерфейс, то какие причины (помимо технической выгоды в том, что не требуется динамическая диспетчеризация с таблицей поиска виртуальных функций и т. Д.) Существуют для того, чтобы эти объекты не наследовали от базового объекта, который объявляет этот интерфейс, таким образом сделать это явным интерфейсом? Другой способ сказать это: можете ли вы дать мне случай, когда два объекта, которые предлагают один и тот же неявный интерфейс (и, следовательно, могут использоваться в качестве типов в образце класса шаблона), не должны наследоваться от базового класса, который делает этот интерфейс явным?
Некоторые похожие посты:
- 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
всегда ожидал бы реализацию a CoolClass
. Но предположим, что этот код будет частью структуры API, поэтому я не могу предсказать, захотят ли пользователи создавать подклассы CoolClass
или прокрутить свой собственный класс, имеющий doSomethingCool()
функцию.
источник
Ответы:
Вы уже определили важный момент: один - время выполнения, а другой - время компиляции . Реальная информация, которая вам нужна, это последствия этого выбора.
Compiletime:
virtual
наследовании или других махинациях с неявными интерфейсами - большое преимущество.Runtime:
Учитывая относительный список, если вам не нужно определенное преимущество наследования во время выполнения, не используйте его. Это медленнее, менее гибко и менее безопасно, чем шаблоны.
Изменить: Стоит отметить , что в C ++ в частности , есть использую для наследования другого , чем время выполнения полиморфизма. Например, вы можете наследовать typedefs, или использовать его для маркировки типов, или использовать CRTP. В конечном счете, однако, эти методы (и другие) действительно подпадают под «время компиляции», даже если они реализованы с использованием
class X : public Y
.источник