Множественное наследование от одного и того же деда - реализации слияния? - PullRequest
3 голосов
/ 18 апреля 2011

для определенного проекта я объявил интерфейс (класс только с чисто виртуальными функциями) и хочу предложить пользователям несколько реализаций этого интерфейса.

Я хочу, чтобы пользователи обладали большой гибкостью, поэтому я предлагаю частичные реализации этого интерфейса. В каждую реализацию включена некоторая функциональность, другие функции не переопределяются, поскольку они заботятся о разных частях.

Однако я также хочу представить пользователям полностью удобную реализацию интерфейса. Поэтому мой первый подход состоял в том, чтобы просто извлечь класс из обеих частичных реализаций. Это не сработало и завершилось с ошибкой, что некоторые функции все еще являются чисто виртуальными в производном классе.

Так что мой вопрос, есть ли способ просто объединить две частичные реализации одного и того же интерфейса. Я нашел обходной путь, явно указав, какую функцию я хочу вызывать для каждого метода, но я считаю это довольно уродливым и был бы благодарен за механизм, который позаботится об этом для меня.

#include <iostream>

class A{
    public:
        virtual void foo() = 0;
        virtual void bar() = 0;
};

class B: public A{
    public:
        void foo(){ std::cout << "Foo from B" << std::endl; }
};

class C: public A{
    public:
        void bar(){ std::cout << "Bar from C" << std::endl; }
};

// Does not work
class D: public B, public C {};

// Does work, but is ugly
class D: public B, public C {
    public:
        void foo(){ B::foo(); }
        void bar(){ C::bar(); }
};

int main(int argc, char** argv){
    D d;
    d.foo();
    d.bar();
}

С уважением, Александр


Фактическая проблема состоит в том, чтобы управлять несколькими посетителями для дерева, позволяя каждому из них пройти по дереву, принять решение для каждого из узлов, а затем объединить решение каждого посетителя и накапливать его в определенное решение.

Разделение обеих частей, к сожалению, невозможно без (я думаю) огромных накладных расходов, так как я хочу предоставить одну реализацию, заботящуюся об управлении посетителями, и одну, заботящуюся о том, как сохранить окончательное решение.

Ответы [ 5 ]

2 голосов
/ 18 апреля 2011

Рассматривали ли вы возможность полностью отказаться от наследования алмазов, предоставив несколько абстрактных классов, каждый с необязательными реализациями, позволяя пользователю смешивать и сопоставлять реализацию и интерфейс по умолчанию при необходимости?

В вашем случае происходит то, что, как только вынаследование D, B::bar не было реализовано и C::foo не было реализовано.Промежуточные классы B и C не могут видеть реализации друг друга.

Если вам нужен полный интерфейс в прародителе, вы рассматривали возможность предоставления реализации другим способом, возможно,политика с шаблонами и классами по умолчанию, которые будут отправлены для обеспечения поведения по умолчанию?

0 голосов
/ 05 марта 2015

Я думаю, что проблема в том, что при использовании простого наследования между B и A и между C и A, вы в конечном итоге получаете два объекта типа A в D (каждый из которых будет иметь чисто виртуальныйфункция, вызывающая ошибку компиляции, потому что D, таким образом, является абстрактным, и вы пытаетесь создать его экземпляр).

Использование виртуального наследования решает проблему, поскольку гарантирует, что в D. только одна копия A

0 голосов
/ 20 апреля 2011

Поскольку вы упомянули, что в основном вам нужен доступ к функциям, а не к элементам данных, вот другой метод, который вы могли бы использовать вместо множественного наследования с использованием шаблонов и частичной специализации шаблонов:

#include <iostream>

using namespace std;

enum class_t { CLASS_A, CLASS_B, CLASS_C };

template<class_t class_type>
class base_type
{
    public:
            static void foo() {}
            static void bar() {}
};

template<>
void base_type<CLASS_A>::foo() { cout << "Calling CLASS_A type foo()" << endl; }

template<>
void base_type<CLASS_B>::bar() { cout << "Calling CLASS_B type bar()" << endl; }

template<>
void base_type<CLASS_C>::foo() { base_type<CLASS_A>::foo(); }

template<>
void base_type<CLASS_C>::bar() { base_type<CLASS_B>::bar(); }

int main()
{
    base_type<CLASS_A> Class_A;
    Class_A.foo();

    base_type<CLASS_B> Class_B;
    Class_B.bar();

    base_type<CLASS_C> Class_C;
    Class_C.foo();
    Class_C.bar();

    return 0;
}

Теперь, если вам нужны нестатические функции, которые имеют доступ к закрытым элементам данных, это может быть немного сложнее, но все же это должно быть выполнимо. Однако, скорее всего, для этого потребуется отдельный класс свойств, который вы можете использовать для доступа к нужным типам, не сталкиваясь с ошибками компилятора «неполных типов».

Спасибо

Jason

0 голосов
/ 18 апреля 2011

Существует также вероятность того, что вы можете использовать "фабричный" класс для основного типа интерфейса. Другими словами, первичный интерфейсный класс также содержит некоторый тип статической функции, которая генерирует соответствующий дочерний класс по запросу от пользователя. Например:

#include <cstdio>

class A 
{
    public:
       enum class_t { CLASS_B, CLASS_C };

       static A* make_a_class(class_t type);

       virtual void foo() = 0;
       virtual void bar() = 0;
};

class B: public A
{
    private:
        virtual void foo() { /* does nothing */ }

    public:
        virtual void bar() { printf("Called B::bar()\n"); }
};

class C: public A
{
    private:
        virtual void bar() { /* does nothing */ }

    public:
        virtual void foo() { printf("Called C::foo()\n"); }
};

A* A::make_a_class(class_t type)
{
   switch(type)
   {
       case CLASS_B: return new B();
       case CLASS_C: return new C();
       default: return NULL;
    }
}

int main()
{
    B* Class_B_Obj = static_cast<B*>(A::make_a_class(A::CLASS_B));
    C* Class_C_Obj = static_cast<C*>(A::make_a_class(A::CLASS_C));

    //Class_B_Obj->foo(); //can't access since it's private
    Class_B_Obj->bar();

    Class_C_Obj->foo();
    //Class_C_Obj->bar(); //can't access since it's private

    return 0;
}

Если class A по какой-то причине требуется доступ к некоторым закрытым членам class B или class C, просто сделайте class A другом дочерних классов (например, вы можете сделать конструкторы class B и class C частные конструкторы, так что только статическая функция в class A может генерировать их, и пользователь не может создать их самостоятельно, не вызвав статическую фабричную функцию в class A).

Надеюсь, это поможет,

Jason

0 голосов
/ 18 апреля 2011

Если ваш интерфейс верхнего уровня имеет логическое разделение по функциональности, вы должны разделить его на два отдельных интерфейса. Например, если у вас есть функции сериализации и рисования в интерфейсе A, вы должны разделить их на два интерфейса: ISerialization и IDrawing.

Вы можете предоставить реализацию по умолчанию для каждого из этих интерфейсов. Пользователь ваших классов может при необходимости наследовать ваш интерфейс или реализацию по умолчанию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...