Где «виртуальное» ключевое слово необходимо в сложной иерархии множественного наследования?

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

Сложность std::set_intersection на входных наборах размера n1 и n2 равна O (n1 + n2). Вы можете взять свои исходные векторы и пересечь их в стиле однократного исключения, то есть в первом раунде вы пересекаете 1-й и 2-й векторы, 3-й и 4-й, 5-й и 6-й и т. Д .; на втором круге вы пересекаете 1-й и 2-й пересечения, 3-й и 4-й и т. д .; повторяйте, пока последний раунд не произведет только одно пересечение. Сумма размеров всех векторов, выживших в каждом раунде, составляет не более половины суммы размеров векторов в начале раунда, поэтому этот алгоритм принимает O (N) время (также O (N) пространство) вообще где N - сумма размеров всех исходных векторов на вашем входе. (Это O (N), потому что N + N / 2 + N / 4 + ... & lt; 2N.)

Таким образом, при вводе, состоящем из уже отсортированных векторов, сложность алгоритма O (N).

Ваш алгоритм объединяет векторы в совершенно другой последовательности, но пока я не уверен на 100%, это также O (N), я сильно подозреваю, что это так.


Изменить: Что касается того, как реально реализовать алгоритм «турнира» на C ++, это зависит от того, насколько вы хотите работать, чтобы оптимизировать это, и в некоторой степени о характере вашего ввода.

] Самый простой подход - создать новый список векторов; возьмите два вектора из старого списка, вставьте вектор в новый список, объедините два старых вектора на новый вектор, уничтожьте старые векторы, надейтесь, что библиотека эффективно управляет памятью.

Если вы хотите уменьшите выделение новых векторов, может помочь повторное использование векторов (как вы уже думали). Если структура входных данных является std::list<std::vector<int> >, например, вы можете начать с нажатия одного пустого вектора на переднем плане этого списка. Сделайте три итератора, один для нового вектора и один для каждого из первых первых двух векторов в списке. Возьмите пересечение векторов на последних двух итераторах, записывая результат в первый итератор, затем очистите векторы на последних двух итераторах. Переместите два последних итератора вперед на два места, переместите первый итератор вперед на одно место. Повторение. Если вы достигли состояния, в котором один из двух последних итераторов достиг конца (), а другой - нет, удалите все элементы списка между первым итератором и другим итератором. Теперь у вас есть список векторов снова и может повторяться до тех пор, пока в списке больше одного.

Если вход std::vector<std::vector<int> >, то нажатие элемента на передний план списка относительно дорого, поэтому вам может понадобиться несколько более сложный алгоритм. Есть много вариантов, никаких очевидных победителей, о которых я могу думать.

13
задан jchl 6 August 2010 в 01:54
поделиться

6 ответов

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

class A;
class B: virtual A;
class C: virtual A;
class D: virtual B;
class E: virtual B, virtual C;
class F: virtual C;
class G:         D, virtual E;
class H: virtual E,         F;
class I:         G,         H;
8
ответ дан 1 December 2019 в 19:30
поделиться

Я придумал программу, которая может помочь вам в изучении тонкостей виртуальных баз. Она печатает иерархию классов под I в виде диграфа, подходящего для graphiviz ( http://www.graphviz.org/ ). Для каждого экземпляра есть счетчик, который также помогает понять порядок построения. Вот программа:

#include <stdio.h>
int counter=0; 



#define CONN2(N,X,Y)\
    int id; N() { id=counter++; }\
    void conn() \
    {\
        printf("%s_%d->%s_%d\n",#N,this->id,#X,((X*)this)->id); \
        printf("%s_%d->%s_%d\n",#N,this->id,#Y,((Y*)this)->id); \
        X::conn(); \
        Y::conn();\
    }
#define CONN1(N,X)\
    int id; N() { id=counter++; }\
    void conn() \
    {\
        printf("%s_%d->%s_%d\n",#N,this->id,#X,((X*)this)->id); \
        X::conn(); \
    }

struct A { int id; A() { id=counter++; } void conn() {} };
struct B : A { CONN1(B,A) };
struct C : A { CONN1(C,A)  };
struct D : B { CONN1(D,B) };
struct E : B,C { CONN2(E,B,C) };
struct F : C { CONN1(F,C) };
struct G : D,E { CONN2(G,D,E) };
struct H : E,F { CONN2(H,E,F) };
struct I : G,H { CONN2(I,G,H) };
int main()
{
    printf("digraph inh {\n");
    I i; 
    i.conn(); 
    printf("}\n");
}

Если я выполню это (g++ base.cc ; ./a.out >h.dot ; dot -Tpng -o o.png h.dot ; display o.png ), то получу типичное невиртуальное дерево базы: alt text

Добавление достаточной виртуальности...

struct B : virtual A { CONN1(B,A) };
struct C : virtual A { CONN1(C,A)  };
struct D : virtual B { CONN1(D,B) };
struct E : virtual B, virtual C { CONN2(E,B,C) };
struct F : virtual C { CONN1(F,C) };
struct G : D, virtual E { CONN2(G,D,E) };
struct H : virtual E,F { CONN2(H,E,F) };
struct I : G,H { CONN2(I,G,H) };

... приводит к форме алмаза (посмотрите на цифры, чтобы узнать порядок построения!!)

alt text

Но если вы сделаете все базы виртуальными:

struct A { int id; A() { id=counter++; } void conn() {} };
struct B : virtual A { CONN1(B,A) };
struct C : virtual A { CONN1(C,A)  };
struct D : virtual B { CONN1(D,B) };
struct E : virtual B, virtual C { CONN2(E,B,C) };
struct F : virtual C { CONN1(F,C) };
struct G : virtual D, virtual E { CONN2(G,D,E) };
struct H : virtual E, virtual F { CONN2(H,E,F) };
struct I : virtual G,virtual H { CONN2(I,G,H) };

Вы получите алмаз с другим порядком инициализации:

alt text

Развлекайтесь!

24
ответ дан 1 December 2019 в 19:30
поделиться

Мое личное предложение - начать с B и C: виртуальный A, а затем продолжать добавлять, пока компилятор не перестанет жаловаться.

На самом деле, я бы сказал, что B и C: виртуальные A, G и H: виртуальные E, а E: виртуальные B и C. Все остальные связи наследования могут быть нормальным наследованием. Однако этому чудовищу потребовалось бы около шести десятилетий, чтобы совершить виртуальный звонок.

2
ответ дан 1 December 2019 в 19:30
поделиться

Отредактировано : я думал, что A был наиболее производным классом;)

@ Ответ Лютера действительно крутой, но вернемся к исходному вопросу:

Вам НЕОБХОДИМО использовать виртуальное наследование, когда наследование от любого класса, от которого в иерархии наследования наследуется хотя бы один другой класс (на диаграммах Лютера это означает, что по крайней мере две стрелки указывают на класс).

Здесь нет необходимости перед D , F , G и H , потому что только один класс является производным от них (и ни один не является производным от них). Я на данный момент).

Однако, если вы заранее не знаете, будет ли другой класс унаследован от вашего базового класса, вы можете добавить виртуальный в качестве меры предосторожности. Например, рекомендуется, чтобы класс Exception фактически унаследовал от std :: exception ни кем иным, как самим Страуструпом.

Как заметил Лютер, он изменяет порядок создания экземпляров (и имеет небольшое влияние на производительность), но я считаю, что любой дизайн, основанный на порядке создания, изначально был неправильным. И точно так же: у вас по-прежнему есть гарантии, что базовые классы инициализируются перед любым атрибутом производного класса и, следовательно, перед выполнением тела производного конструктора.

0
ответ дан 1 December 2019 в 19:30
поделиться

Если вы хотите убедиться, что объект верхнего класса в иерархии ( I в вашем случае) содержит ровно один подобъект каждого родительского класса, вы должны найти все классы в вашей иерархии, которые имеют более одного суперкласса и делают эти классы виртуальными базами своих суперклассов. Вот и все.

В вашем случае классы A , B , C и E должны становиться виртуальными базовыми классами каждый раз, когда вы наследуете от них в этой иерархии.

Классы D , F , G и H не должны становиться виртуальными базовыми классами.

1
ответ дан 1 December 2019 в 19:30
поделиться

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

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

0
ответ дан 1 December 2019 в 19:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: