Какой самый простой способ удовлетворить чистый абстрактный метод с помощью методов из других базовых классов
Изменить: За некоторые комментарии под простым я имею в виду а) меньше кода, б) простота в обслуживании и в) трудно ошибиться.
Редактирование № 2: Кроме того, использование сдерживания вместо частного наследования не является нежелательным, если оно действительно упрощает реализацию InterfaceImpl
,
В настоящее время я знаю, что единственный способ сделать это - заставить реализатор определить абстрактный метод и делегировать вызов методу целевого базового типа. Пример:
#include <iostream>
#include <memory>
class Interface
{
public:
virtual void method1() = 0;
virtual void method2(int x) = 0;
};
class MethodOneImpl
{
private:
void method1(int x)
{ std::cout << "MethodOneImpl::method1() " << x << std::endl; }
public:
void method1() { method1(0); }
};
class MethodTwoImpl
{
public:
void myFunc(int x)
{ std::cout << "MethodTwoImpl::myFunc(x)" << x << std::endl; }
};
class InterfaceImpl : public Interface
, private MethodOneImpl
, private MethodTwoImpl
{
public:
virtual void method1() { MethodOneImpl::method1(); }
virtual void method2(int x) { MethodTwoImpl::myFunc(x); }
};
int main()
{
std::unique_ptr<Interface> inf;
inf.reset(new InterfaceImpl);
inf->method1();
inf->method2(0);
// This should be disallowed!
// std::unique_ptr<MethodOneImpl> moi;
// moi.reset(new InterfaceImpl);
}
Сначала я подумал, что, возможно, это решит проблему:
class InterfaceImpl : public Interface
, private MethodOneImpl
, private MethodTwoImpl
{
public:
using MethodOneImpl::method1;
// Obviously this wouldn't work as the method names don't match.
//using MethodTwoImpl::???
};
Первое использование оператора сделает оба MethodOneImpl::method1
методы должны быть публичными, но на самом деле они не выполняют контракт с Interface
и это изменяет доступность MethodOneImpl::method1(int)
, И, очевидно, мы не могли использовать это решение с method2
как имена не совпадают.
FWIW, у меня есть то, что я считаю решением, но оно вообще не является частью стандарта (другими словами, оно не будет компилироваться). Я думал о внесении предложения в комитет C++; если у кого-то есть совет, я буду признателен за любые комментарии ниже (но, пожалуйста, не отправляйте совет в качестве ответа).
5 ответов
Другой вариант (по крайней мере, при использовании MS VC++) - использовать виртуальное наследование:
struct MyInterface
{
virtual void Method1() = 0;
virtual void Method2() = 0;
};
class Method1Impl : public virtual MyInterface
{
virtual void Method1() { _tprintf( _T("Method1\n") ); }
};
class Method2Impl : public virtual MyInterface
{
virtual void Method2() { _tprintf( _T("Method2\n") ); }
};
class InterfaceImpl : public virtual MyInterface,
private Method1Impl,
private Method2Impl
{
};
void TestWeirdInterfaceImpl()
{
MyInterface* pItf = new InterfaceImpl();
pItf->Method1();
pItf->Method2();
}
Хотя это, кажется, работает и удовлетворяет тому, что вы ищете (кроме предупреждения C4250 о том, что вам придется подавлять с помощью #pragma), это не будет моим подходом. (Я считаю, что виртуальное наследование все еще не поддерживается всеми компиляторами, но я могу ошибаться).
Я бы, вероятно, пошел с сдерживанием, и как только шаблонный код станет идентификатором, оберните его в какую-то макроплану (похожую на карты в ATL или MFC), которая сделает его действительно очень трудным, чтобы когда-либо облажаться.
Так что это мой макроподход:
struct MyInterface
{
virtual float Method1( int x ) = 0;
virtual int Method2( float a, float b ) = 0;
virtual void Method3( const TCHAR* sz ) = 0;
};
class Method1Impl
{
public:
float Method1( int x ) {
_tprintf( _T("Method1: %d\n"), x ); return 5.0;
}
};
class Method2and3Impl
{
public:
int Method2( float a, float b ) {
_tprintf( _T("Method2: %f, %f\n"), a, b ); return 666;
}
void Method3( const TCHAR* sz ) {
_tprintf( _T("Method3: %s"), sz );
}
};
#define DECLARE_METHOD0( MethodName, Obj, R ) \
virtual R MethodName() { return Obj.MethodName(); }
#define DECLARE_METHOD1( MethodName, Obj, R, A1 ) \
virtual R MethodName( A1 a1 ) { return Obj.MethodName( a1 ); }
#define DECLARE_METHOD2( MethodName, Obj, R, A1, A2 ) \
virtual R MethodName( A1 a1, A2 a2 ) { return Obj.MethodName( a1, a2 ); }
class InterfaceImpl : public MyInterface
{
public:
DECLARE_METHOD1( Method1, m_method1Impl, float, int );
DECLARE_METHOD2( Method2, m_method2and3Impl, int, float, float );
DECLARE_METHOD1( Method3, m_method2and3Impl, void, const TCHAR* );
private:
Method1Impl m_method1Impl;
Method2and3Impl m_method2and3Impl;
};
void TestWeirdInterfaceImpl()
{
MyInterface* pItf = new InterfaceImpl();
pItf->Method1( 86 );
pItf->Method2( 42.0, 24.0 );
pItf->Method3( _T("hi") );
}
Пока боги C++ не наделяют нас переменными макросами, вам придется объявлять один для каждого количества параметров, которые у вас есть. Также, если вы используете множественное наследование, потенциально вам не понадобится второй параметр "Obj", но, как я уже говорил, я бы избежал множественного наследования, если есть другое решение, которое в данном случае является одним дополнительным параметром.
Тем не менее, третьим вариантом может быть то, что авторы Pragmatic Programmer, похоже, много защищают. Если у вас есть тонна печенья код, который вы не хотите повторять, потому что, как вы указали, он вводит человеческую ошибку. Определите свой собственный язык и напишите скрипт генератора кода (python, perl...) для автоматического создания реального кода. В этом случае вы можете почти указать на интерфейс и попросить сценарий написать для вас текст. Я сам не пытался делать подобные вещи, но в последнее время хотел использовать это где-то, чтобы увидеть и оценить результат.
class AbsInterface
{
// this is a simple interface class.
public:
virtual void Method1() = 0;
virtual void Method2() = 0;
};
class Functor1
{
public:
void operator () ()
{
printf("This Is Void Functor1");
}
};
class Functor2
{
public:
void operator () ()
{
printf("This Is void Functor2");
}
};
template <class T1, class T2>
class DerivedTemplateClass : public AbsInterface
{
public:
virtual void Method1() { T1()(); }
virtual void Method2() { T2()(); }
};
void main()
{
DerivedTemplateClass<Stratege1, Stratege2> instance;
instance.Method1();
instance.Method2();
}
как видите, я использовал Functor. Вы можете работать с шаблоном и функтором.
Служит ли это вашей цели? Он поддерживает отношения интерфейса и дает вам поддерживаемый код, не обращая внимания на код клиента.
Разделение каждого метода в functionoid и предоставление вам возможности управлять прототипом каждого метода другого базового класса.
#include <iostream>
#include <memory>
using namespace std;
//No Control over this.
class MethodOneImpl
{
private:
void method1(int x)
{ std::cout << "MethodOneImpl::method1() " << x << std::endl; }
public:
void method1() { method1(0); }
};
class MethodTwoImpl
{
public:
void myFunc(int x)
{ std::cout << "MethodTwoImpl::myFunc(x)" << x << std::endl; }
};
//*************************//
class Interface
{
public:
virtual void method1() = 0;
virtual void method2(int x) = 0;
};
//This is what i would do. //
class BaseFuncType
{
//no pure virtual
void Call()
{
throw "error";
}
void Call(int x)
{
throw "error";
}
};
class Method1: public BaseFuncType
{
auto_ptr<MethodOneImpl> MethodPtr;
public:
Method1()
{
MethodPtr.reset(new MethodOneImpl());
}
virtual int Call()
{
MethodPtr->method1();
}
};
class Method2: public BaseFuncType
{
auto_ptr<MethodTwoImpl> MethodPtr;
public:
Method2()
{
MethodPtr.reset(new MethodTwoImpl());
}
virtual int Call(int x)
{
MethodPtr->myFunc(x);
}
};
template <class T1>
class MethodFactory
{
private:
T1 methodObj;
public:
void CallMethod()
{
methodObj.Call();
}
void CallMethod(int x)
{
methodObj.Call(x);
}
};
class InterfaceImpl : public Interface
{
auto_ptr<MethodFactory> factory;
public:
virtual void method1()
{
factory.reset(new MethodFactory<Method1>());
factory->CallMethod();
}
virtual void method2(int x)
{
factory.reset(new MethodFactory<Method2>());
factory->CallMethod(x);
}
};
int main()
{
auto_ptr<Interface> inf;
inf.reset(new InterfaceImpl);
inf->method1();
inf->method2(10);
// This should be disallowed!
// std::unique_ptr<MethodOneImpl> moi;
// moi.reset(new InterfaceImpl);
}
Кажется невозможным принести MethodOneImpl
/ MethodTwoImpl
в сферу Interface
не имея их наследовать от Interface
потому что они не будут заполнять виртуальную таблицу, если они этого не делают. C++ пропускает что-то вроде ключевого слова implements
с других языков.
Таким образом, вы застряли в виртуальном наследовании, если не понимаете / не принимаете, что то, что вы ищете, это просто шаблон моста, который не удовлетворяет требованию a) (вы должны написать больше кода), в середине b) (код не обязательно трудно поддерживать) и может удовлетворить в).
Здесь (другое) возможное решение (с единственным методом, хотя уменьшить вздутие живота)
class Interface
{ public:
virtual void method1() {return impl_->method1();}
private:
Interface() {}
protected:
struct Impl {
virtual void method1() = 0; };
std::shared_ptr<Impl> impl_;
Interface(const std::shared_ptr<Impl> &impl) : impl_(impl) {}
};
class InterfaceImpl : public Interface
{
struct Impl : public Interface::Impl {
void method1() { std::cout << "InterfaceImpl::method1() " << std::endl; } } ;
public:
InterfaceImpl() : Interface(std::shared_ptr<Impl> (new Impl)) {}
};
template <class T>
class GenericInterfaceImpl : public Interface {
struct Impl : public Interface::Impl {
Impl( T &t) : t_(t) {}
void method1() { t_.method1() ; }
T t_; };
public:
GenericInterfaceImpl() : Interface(std::shared_ptr<Impl> (new Impl(T()))) {}
};
struct AMethod1Impl {
void method1() { std::cout << "AMethod1Impl::method1() " << std::endl; } } ;
struct AnotherMethod1Impl_not_working {
void method1_not_present() { std::cout << "AnotherMethod1Impl_not_working ::method1_not_present() " << std::endl; } } ;
int main() {
// compilation of next line would fail
// (lame attempt to simulate ompilation fail when pure function not implemented)
// Interface inf;
std::unique_ptr<Interface> inf;
inf.reset(new InterfaceImpl);
inf->method1();
inf.reset(new GenericInterfaceImpl<AMethod1Impl>() );
inf->method1();
// compilation of next line would fail
// inf.reset(new GenericInterfaceImpl<AnotherMethod1Impl_not_working>() );
}
Это некрасиво и может привести к увеличению размера исполняемого файла, но как насчет
#include <iostream>
class Interface
{
public:
virtual void method1() = 0;
virtual void method2(int x) = 0;
};
template<typename T>
class MethodOneImpl : public T
{
private:
void method1(int x)
{ std::cout << "MethodOneImpl::method1() " << x << std::endl; }
public:
void method1() { method1(0); }
};
template<typename T>
class MethodTwoImpl : public T
{
public:
void method2(int x)
{ std::cout << "MethodTwoImpl::myFunc(x)" << x << std::endl; }
};
class InterfaceImpl : public MethodTwoImpl<MethodOneImpl<Interface> >
{
};
int main()
{
InterfaceImpl impl;
impl.method1();
impl.method2(0);
}