Иерархия классов Матрицы C++

Если матричная библиотека программного обеспечения имеет корневой класс (например, MatrixBase) от которого более специализированный (или более ограниченный) матричные классы (например, SparseMatrix, UpperTriangluarMatrix, и т.д.), происходят? Если так, производные классы должны быть получены публично/охранительно/конфиденциально? В противном случае они должны быть составлены с классом реализации, инкапсулирующим общую функциональность, и быть в других отношениях не связаны? Что-то еще?

Я разговаривал об этом с коллегой разработчика программного обеспечения (я не по сути), кто упомянул, что это - общая ошибка дизайна программирования получить более ограниченный класс из более общего (например, он использовал пример того, как это не была хорошая идея получить a Circle класс от Ellipse класс как подобный матричным вопросам проектирования), даже когда это верно это a SparseMatrix "" MatrixBase. Интерфейс, представленный и основой и производными классами, должен быть тем же для основных операций; для специализированных операций производный класс имел бы дополнительную функциональность, которую не могло бы быть возможно реализовать для произвольного MatrixBase объект. Например, мы можем вычислить cholesky разложение только для a PositiveDefiniteMatrix объект класса; однако, умножение скаляром должно работать тот же путь и к основе и к производным классам. Кроме того, даже если базовая реализация хранения данных отличается operator()(int,int) должен работать как ожидалось на любой тип матричного класса.

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

9
задан Cœur 22 January 2017 в 15:21
поделиться

6 ответов

Проблема с подклассом Circle для Ellipse (или подклассом Square для Rectangle) возникает, когда вы можете изменить одно измерение в интерфейсе Ellipse, чтобы круг больше не был кругом (и квадрат больше не квадрат) .

Если вы разрешаете только немодифицируемые матрицы, то вы в безопасности и можете естественным образом структурировать иерархию типов.

4
ответ дан 4 December 2019 в 23:05
поделиться

Это хороший вопрос, но я еще не уверен, по каким метрикам вы хотите его оценить.

Как бы то ни было, единственная библиотека Matrix, которую я сейчас использую чаще всего, - это Armadillo , у которого есть общий объект Base , использующий любопытно повторяющийся шаблон remplate . Я считаю, что Eigen (еще одна недавно созданная матричная библиотека с большим количеством шаблонов) делает то же самое.

1
ответ дан 4 December 2019 в 23:05
поделиться

Будет ли полезна возможность иметь базовый класс Matrix, у которого есть методы, позволяющие построить конкретную матрицу? Например, что-то вроде (очень простой пример):

MatrixClass m;
m.buildRotationMatrix(/*params*/)
// Now m is a rotation matrix

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

0
ответ дан 4 December 2019 в 23:05
поделиться

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

В отличие от Java, не каждому классу или структуре нужен базовый класс. Помните о простоте; сложность делает проекты более длинными, сложными в управлении и трудными для исправления.

0
ответ дан 4 December 2019 в 23:05
поделиться

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

Вам стоит прислушаться к своему другу, только я надеюсь, что они не говорят, что SparseMatrix "is-a" MatrixBase. Этот термин означает разные вещи в реальном мире и в мире моделирования. В мире моделирования "is-a" означает следование принципу замещения Лискова (поищите!). Альтернативно это означает, что SparseMatrix должна следовать контракту MatrixBase в том, что функции-члены не должны требовать никаких дополнительных предварительных условий и должны удовлетворять не меньшему количеству постусловий.

Я не знаю, как именно это относится к проблеме матрицы, но если вы изучите термины, которые я использовал в предыдущем параграфе (LSP и Design by Contract), то вы будете на пути к получению ответа на вашу проблему.

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

1
ответ дан 4 December 2019 в 23:05
поделиться

Основная проблема, которую следует остерегаться с подобными проектами на основе наследования, - это SLICING.

Допустим, MatrixBase определяет не виртуальный оператор присваивания. Он копирует все элементы данных, общие для всех подклассов матриц. Ваш класс SparseMatrix определяет дополнительные члены данных.Что же происходит, когда мы пишем это?

SparseMatrix sm(...);
MatrixBase& bm = sm;
bm = some_dense_matrix;

Этот код не имеет особого смысла (пытается назначить DenseMatrix для SparseMatrix напрямую через оператор, определенный в базовом классе) и подвержен всевозможным неприятным действиям нарезки, но является уязвимым аспект такого кода, и существует очень большая вероятность того, что это произойдет где-то в будущем, если вы предоставите операторы присваивания, доступные через MatrixBase * / MatrixBase &. Даже когда у нас есть это:

SparseMatrix sm(...);
MatrixBase& bm = sm;
bm = some_other_sparase_matrix;

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

Обычно следует избегать операторов присваивания для базовых классов! Представьте себе случай, когда Dog и Cat наследуют от Mammal, а Mammal предоставили оператор присваивания, виртуальный или нет. Это означало бы, что мы можем назначать собак кошкам, что не имеет смысла, и даже если бы оператор был виртуальным, было бы трудно обеспечить какое-либо значимое поведение, чтобы назначить млекопитающих другим млекопитающим.

Допустим, мы пытаемся улучшить ситуацию, реализуя оператор присваивания в Dog, чтобы его можно было назначать только другим собакам.Что же происходит, когда мы наследуем от Dog, чтобы создать чихуахуа и добермана? У нас не должно быть возможности назначать чихуахуа доберманам, и поэтому исходный случай рекурсивно повторяется, пока вы не будете уверены, что достигли конечных узлов иерархии наследования (жаль, что в C ++ нет последнего ключевого слова для предотвращения любого дальнейшее наследование).

Та же проблема очевидна с обычным примером «Круг наследует Эллипс». Для круга может потребоваться соответствие ширины и высоты: это инвариант, который класс хочет поддерживать, но любой может просто получить базовый указатель (Ellipse *), указывающий на объект Circle, и нарушить это правило.

Если есть сомнения, избегайте наследования, так как это одна из наиболее часто неправильно используемых функций C ++ и любого языка, поддерживающего объектно-ориентированное программирование в целом. Вы можете попытаться обойти проблему, предоставив механизмы времени выполнения, чтобы определить тип подкласса, назначаемого другому подклассу, и разрешить только сопоставление типов, но теперь вы выполняете много дополнительной работы и несете накладные расходы времени выполнения. Лучше избегать всех операторов присваивания для иерархий наследования и полагаться на такие методы, как clone для создания копий (шаблон прототипа).

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

1
ответ дан 4 December 2019 в 23:05
поделиться
Другие вопросы по тегам:

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