Наследовать интерфейсы, которые совместно используют имя метода
В двух базовых классах есть одно и то же имя функции. Я хочу унаследовать их обоих, и по-другому использовать каждый метод по-разному. Как я могу это сделать с помощью отдельного объявления и определения (вместо определения в определении класса)?
#include <cstdio>
class Interface1{
public:
virtual void Name() = 0;
};
class Interface2
{
public:
virtual void Name() = 0;
};
class RealClass: public Interface1, public Interface2
{
public:
virtual void Interface1::Name()
{
printf("Interface1 OK?\n");
}
virtual void Interface2::Name()
{
printf("Interface2 OK?\n");
}
};
int main()
{
Interface1 *p = new RealClass();
p->Name();
Interface2 *q = reinterpret_cast<RealClass*>(p);
q->Name();
}
Мне не удалось переместить определение в VC8. Я нашел ключевое слово Microsoft Specific Keyword __interface, успешно выполнив эту работу, код ниже:
#include <cstdio>
__interface Interface1{
virtual void Name() = 0;
};
__interface Interface2
{
virtual void Name() = 0;
};
class RealClass: public Interface1,
public Interface2
{
public:
virtual void Interface1::Name();
virtual void Interface2::Name();
};
void RealClass::Interface1::Name()
{
printf("Interface1 OK?\n");
}
void RealClass::Interface2::Name()
{
printf("Interface2 OK?\n");
}
int main()
{
Interface1 *p = new RealClass();
p->Name();
Interface2 *q = reinterpret_cast<RealClass*>(p);
q->Name();
}
но есть ли другой способ сделать это что-то более общее, которое будет работать в других компиляторах?
Ответы
Ответ 1
Эта проблема возникает не очень часто. Решение, с которым я знаком, было разработано Дугом Макилрой и появилось в книгах Бьярна Страуструпа (представлено в разделе "Дизайн и эволюция раздела С++" 12.8 и "Язык программирования С++", раздел 25.6). Согласно обсуждению в "Дизайн и эволюция", было предложено элегантно обработать этот конкретный случай, но оно было отвергнуто, потому что "такие столкновения имен вряд ли станут достаточно распространенными, чтобы гарантировать отдельную функцию языка" и "вряд ли станут повседневными работа для новичков."
Вам нужно не только вызывать Name()
с помощью указателей на базовые классы, вам нужно указать, какой Name()
вы хотите использовать при производном классе. Решение добавляет некоторую косвенность:
class Interface1{
public:
virtual void Name() = 0;
};
class Interface2{
public:
virtual void Name() = 0;
};
class Interface1_helper : public Interface1{
public:
virtual void I1_Name() = 0;
void Name() override
{
I1_Name();
}
};
class Interface2_helper : public Interface2{
public:
virtual void I2_Name() = 0;
void Name() override
{
I2_Name();
}
};
class RealClass: public Interface1_helper, public Interface2_helper{
public:
void I1_Name() override
{
printf("Interface1 OK?\n");
}
void I2_Name() override
{
printf("Interface2 OK?\n");
}
};
int main()
{
RealClass rc;
Interface1* i1 = &rc;
Interface2* i2 = &rc;
i1->Name();
i2->Name();
rc.I1_Name();
rc.I2_Name();
}
Не очень, но решение было не нужно часто.
Ответ 2
В прошлом мне приходилось делать что-то подобное, хотя в моем случае мне нужно было наследовать один интерфейс дважды и иметь возможность различать вызовы, сделанные на каждом из них, я использовал шаблонную прокладку, чтобы помочь мне...
Что-то вроде этого:
template<class id>
class InterfaceHelper : public MyInterface
{
public :
virtual void Name()
{
Name(id);
}
virtual void Name(
const size_t id) = 0;
}
Затем вы выходите из InterfaceHelper
дважды, а не из MyInterface
дважды, и вы указываете другой id
для каждого базового класса. Затем вы можете передать два интерфейса независимо, выполнив правильное InterfaceHelper
.
Вы можете сделать что-то немного более сложное;
class InterfaceHelperBase
{
public :
virtual void Name(
const size_t id) = 0;
}
class InterfaceHelper1 : public MyInterface, protected InterfaceHelperBase
{
public :
using InterfaceHelperBase::Name;
virtual void Name()
{
Name(1);
}
}
class InterfaceHelper2 : public MyInterface, protected InterfaceHelperBase
{
public :
using InterfaceHelperBase::Name;
virtual void Name()
{
Name(2);
}
}
class MyClass : public InterfaceHelper1, public InterfaceHelper2
{
public :
virtual void Name(
const size_t id)
{
if (id == 1)
{
printf("Interface 1 OK?");
}
else if (id == 2)
{
printf("Interface 2 OK?");
}
}
}
Обратите внимание, что выше не было компилятора...
Ответ 3
Вы не можете переопределить их отдельно, вы должны переопределить оба момента:
struct Interface1 {
virtual void Name() = 0;
};
struct Interface2 {
virtual void Name() = 0;
};
struct RealClass : Interface1, Interface2 {
virtual void Name();
};
// and move it out of the class definition just like any other method:
void RealClass::Name() {
printf("Interface1 OK?\n");
printf("Interface2 OK?\n");
}
Вы можете имитировать индивидуальное переопределение с промежуточными базовыми классами:
struct RealClass1 : Interface1 {
virtual void Name() {
printf("Interface1 OK?\n");
}
};
struct RealClass2 : Interface2 {
virtual void Name() {
printf("Interface2 OK?\n");
}
};
struct RealClass : RealClass1, RealClass2 {
virtual void Name() {
// you must still decide what to do here, which is likely calling both:
RealClass1::Name();
RealClass2::Name();
// or doing something else entirely
// but note: this is the function which will be called in all cases
// of *virtual dispatch* (for instances of this class), as it is the
// final overrider, the above separate definition is merely
// code-organization convenience
}
};
Кроме того, вы неправильно используете reinterpret_cast, вы должны иметь:
int main() {
RealClass rc; // no need for dynamic allocation in this example
Interface1& one = rc;
one.Name();
Interface2& two = dynamic_cast<Interface2&>(one);
two.Name();
return 0;
}
И здесь перепишите CRTP, который может быть тем, что вы хотите (или нет):
template<class Derived>
struct RealClass1 : Interface1 {
#define self (*static_cast<Derived*>(this))
virtual void Name() {
printf("Interface1 for %s\n", self.name.c_str());
}
#undef self
};
template<class Derived>
struct RealClass2 : Interface2 {
#define self (*static_cast<Derived*>(this))
virtual void Name() {
printf("Interface2 for %s\n", self.name.c_str());
}
#undef self
};
struct RealClass : RealClass1<RealClass>, RealClass2<RealClass> {
std::string name;
RealClass() : name("real code would have members you need to access") {}
};
Но обратите внимание, что здесь вы не можете позвонить Name на RealClass сейчас (с виртуальной отправкой, например rc.Name()
), вы должны сначала выбрать базу. Макросъемка - это простой способ очистки CRTP-трансляций (обычно доступ к членству гораздо чаще встречается в базе CRTP), но это может быть улучшено. Там короткое обсуждение виртуальной отправки в одном из моих других ответов, но, безусловно, лучше, если у кого есть ссылка.
Ответ 4
class BaseX
{
public:
virtual void fun()
{
cout << "BaseX::fun\n";
}
};
class BaseY
{
public:
virtual void fun()
{
cout << "BaseY::fun\n";
}
};
class DerivedX : protected BaseX
{
public:
virtual void funX()
{
BaseX::fun();
}
};
class DerivedY : protected BaseY
{
public:
virtual void funY()
{
BaseY::fun();
}
};
class DerivedXY : public DerivedX, public DerivedY
{
};
Ответ 5
Есть два других связанных вопроса, которые задают почти (но не полностью) одинаковые вещи:
Выбор из унаследованных имен общих методов. Если вы хотите, чтобы rc.name() вызывал ic1- > имя() или ic2- > name().
Переопределение имен общих методов из (шаблонных) базовых классов. Это имеет более простой синтаксис и меньше кода, чем ваше принятое решение, но не позволяет получить доступ к функциям из производного класса. Более того, если вам не нужно называть name_i1() из rc, вам не нужно использовать такие вещи, как InterfaceHelper.