C ++: дизайн и стоимость для тяжелых иерархий множественного наследования - PullRequest
2 голосов
/ 24 февраля 2012

У меня есть иерархия классов со следующими тремя классами:

template<int pdim >
class Function
{
   virtual double operator()( const Point<pdim>& x) const = 0;
};

, которая является функцией в pdim-мерном пространстве и возвращает значение типа double.

template<int pdim, int ldim >
class NodeFunction
{
   virtual double operator()( const Node<pdim,ldim>& pnode, const Point<ldim>& xLoc) const = 0;
};

которая является функцией изldim-мерное локальное пространство узла в pdim-мерном пространстве.

template<int pdim, int ldim, int meshdim >
class PNodeFunction
{
   virtual double operator()( const PNode<pdim,ldim,meshdim>& pnode, const Point<ldim>& xLoc) const = 0;
};

Причина 1 для этой схемы: функция NodeFunction является более общей, чем функция.Он всегда может отобразить локальную точку ldim-точки на pdim-точку.Например, ребро (Узел с ldim = 1) отображает интервал [0,1] в физическое пространство pdim.Вот почему каждая функция является NodeFunction .Функция NodeFunction является более общей, поскольку NodeFunction разрешено запрашивать у узла атрибуты.

Причина 2 для этой схемы: функция PNodeFunction является более общей, чем функция NodeFunction.Ровно один Узел связан с каждым PNode (не наоборот).Вот почему каждая PNodeFunction является NodeFunction .Функция PNode является более общей, поскольку она также имеет весь контекст PNode, который является частью Mesh (таким образом, он знает всех своих родителей, соседей, ...).

Сводка :Каждый Function<pdim> - это NodeFunction<pdim, ldim> для любого параметра ldim.Каждый NodeFunction<pdim, ldim> - это NodeFunction<pdim, ldim, meshdim> для любого параметра meshdim.

Вопрос : Каков наилучший способ выразить это в C ++, чтобы я мог использовать Functionвместо NodeFunction / PNodeFunction, чтобы код был быстрым (это высокопроизводительный вычислительный код), чтобы код работал для

Параметры шаблона не являются полностью независимыми, а скорее зависят отдруг друга: - pdim=1,2,3 (основной интерес), но было бы хорошо, если бы он работал и для значений pdim до 7. - 'ldim = 0,1, ..., pdim' - 'meshdim = ldim, ldim + 1, ..., pdim '

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

Варианты

Я подумал о нескольких способах реализации этого (в настоящее время я реализовал Вариант 1).Я написал это здесь, чтобы вы могли рассказать мне о преимуществах и недостатках этих подходов.

Вариант 1

Реализация описанного выше наследования A<dim> inherits from B<dim,dim2> через вспомогательный шаблон Arec<dim,dim2>.В псевдокоде это

class A<dim> : public Arec<dim,dim>;
class Arec<dim,dim2> : public Arec<dim,dim2-1>, public B<dim,dim2>;
class Arec<dim,0> : public B<dim,dim2>;

Это применяется как для наследования Function от NodeFunction, так и NodeFunction от PNodeFunction.Как NodeFunction наследует примерно O(pdim^2) раз от PNodeFunction, как это масштабируется?Неужели эта огромная виртуальная таблица плоха?

Примечание : Фактически каждая функция также должна наследоваться от VerboseObject, что позволяет мне выводить отладочную информацию о функции, например, std::cout.Я делаю это практически по наследству PNodeFunction от VerboseObject.Как это повлияет на производительность?Это должно увеличить время для создания функции и печати отладочной информации, но не время для operator(), верно?

Вариант 2

Не выражать наследование в C ++,например, A<dim> не наследуется от B<dim,dim2> bur, скорее, есть функция для преобразования двух

class AHolder<dim,dim2> : public B<dim, dim> {

}

std::shared_pointer< AHolder<dim,dim2> > interpretAasB( std::shared_pointer< AHolder<dim> >)
 [...]

Это имеет тот недостаток, что я больше не могу использовать Function<dim> вместо NodeFunction<dim> или PNodeFunction<dim>.

Вариант 3

Каков ваш предпочтительный способ реализации этого?

1 Ответ

1 голос
/ 29 февраля 2012

Я не очень хорошо понимаю вашу проблему; это может быть связано с тем, что мне не хватает конкретных знаний о проблемной области. В любом случае кажется, что вы хотите сгенерировать иерархию классов с функцией (наиболее производный класс) внизу и PNodeFunction вверху (наименее производный класс).
Для этого я могу только порекомендовать книгу о современном дизайне C ++ Александреску, особенно главу о генераторах иерархии.
Существует библиотека с открытым исходным кодом, основанная на книге под названием Loki. Вот часть, которая может вас заинтересовать.

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

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

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