Имеет ли смысл использовать объекты (вместо примитивных типов) для всего в C ++?

17

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

static bool getGPS(double plane_latitude, double plane_longitude, double plane_altitude,
                       double plane_roll, double plane_pitch, double plane_heading,
                       double gimbal_roll, double gimbal_pitch, double gimbal_yaw, 
                       int target_x, int target_y, double zoom,
                       int image_width_pixels, int image_height_pixels,
                       double & Target_Latitude, double & Target_Longitude, double & Target_Height);

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

static GPSCoordinate getGPS(GPSCoordinate plane, Angle3D planeAngle, Angle3D gimbalAngle,
                            PixelCoordinate target, ZoomLevel zoom, PixelSize imageSize)

Мне кажется, это гораздо более читабельно и безопасно, чем первый метод. Но имеет ли смысл создавать PixelCoordinateи PixelSizeзанятия? Или мне лучше использовать только std::pair<int,int>для каждого. И имеет ли смысл иметь ZoomLevelкласс, или я должен просто использовать double?

Моя интуиция в использовании классов для всего основана на следующих предположениях:

  1. Если есть классы для всего, было бы невозможно передать туда, ZoomLevelгде Weightожидался объект, поэтому было бы сложнее предоставить неправильные аргументы функции
  2. Аналогично, некоторые недопустимые операции могут вызвать ошибки компиляции, такие как добавление a GPSCoordinateк ZoomLevelдругому или другому.GPSCoordinate
  3. Легальные операции будут легко представлять и безопасны. т.е. вычитание двух GPSCoordinates дастGPSDisplacement

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

zackg
источник
8
«Большая часть кода C ++, который я видел, использует много примитивных типов» - приходят на ум две мысли: большая часть кода C ++ могла быть написана программистами, знакомыми с C, которая имеет более слабую абстракцию; также закон осетровых
congusbongus
3
И я думаю, это потому, что примитивные типы просты, понятны, быстры, изящны и эффективны. Теперь, в примере с OP, определенно хорошая идея представить несколько новых классов. Но ответ на заглавный вопрос (где написано «все») - оглушительное нет!
Мистер Листер
1
@Max typedeffing of doubles абсолютно ничего не делает для решения проблемы OP.
Мистер Листер
1
@CongXu: У меня была почти та же мысль, но больше в том смысле, что «много кода на любом языке могло быть написано программистами, у которых вообще были проблемы с построением абстракций».
Док Браун,
1
Как говорится, «если у вас есть функция с 17 параметрами, вы, вероятно, пропустили несколько».
Йорис Тиммерманс

Ответы:

24

Да, безусловно. Функции / методы, которые принимают слишком много аргументов, являются запахом кода и указывают по крайней мере на одно из следующего:

  1. Функция / метод делает слишком много вещей одновременно
  2. Функция / метод требует доступа ко многим вещам, потому что они просят, а не говорят или нарушают какой-то закон о проектировании ОО
  3. Аргументы на самом деле тесно связаны

Если это последний случай (и ваш пример, безусловно, так), самое время сделать какую-нибудь из этих «абстрактных штанов » , о которых крутые парни говорили о, всего несколько десятилетий назад. На самом деле, я бы пошел дальше вашего примера и сделал бы что-то вроде:

static GPSCoordinate getGPS(Plane plane, Angle3D gimbalAngle, GPSView view)

(Я не знаком с GPS, так что это, вероятно, неправильная абстракция; например, я не понимаю, как зум имеет отношение к вызываемой функции getGPS.)

Пожалуйста, предпочитайте классы как PixelCoordinateболее std::pair<T, T>, даже если у обоих одинаковые данные. Это добавляет семантическое значение, плюс немного дополнительной безопасности типов (компилятор не даст вам передать ScreenCoordinatea, PixelCoordinateдаже если оба pairs.

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

Отказ от ответственности: я предпочитаю C на C ++

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

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

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

Если есть классы для всего, было бы невозможно передать ZoomLevel туда, где ожидался объект Weight, поэтому было бы сложнее предоставить неправильные аргументы функции

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

Аналогично, некоторые недопустимые операции могут вызвать ошибки компиляции, такие как добавление GPSCoordinate к ZoomLevel или другой GPSCoordinate.

Хорошее использование перегрузок операторов.

Юридические операции будут легко представлять и безопасны. т.е. вычитание двух GPSCoordinates даст GPSDisplacement

Также хорошо использовать операторские перегрузки.

Я думаю, что вы на правильном пути. Еще одна вещь, которую стоит рассмотреть, это то, как это вписывается в остальную часть программы.

beatgammit
источник
3

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

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

Я бы порекомендовал использовать typedefs, которые являются просто псевдонимами для примитивных типов ( typedef double ZoomLevel) не только по этой причине, но и по другой: он легко позволяет позже изменить тип. Когда вы однажды поймете, что использование floatможет быть таким же хорошим, doubleно может быть более эффективным с точки зрения памяти и процессора, вам просто нужно изменить это в одном месте, а не в десятках раз.

Philipp
источник
+1 за предложение typedef. Получает вам лучшее из обоих миров: примитивных типов и объектов.
Карл Билефельдт
Нетруднее изменить тип члена класса, чем typedef; или ты имел ввиду по сравнению с примитивами?
MaHuJa
2

Прекрасные ответы здесь уже есть, но я хотел бы добавить одну вещь:

Использование PixelCoordinateи PixelSizeкласс делает вещи очень специфичными. Генерал Coordinateили Point2Dкласс, возможно, лучше подойдет вашим потребностям. A Point2Dдолжен быть классом, реализующим наиболее распространенные точечные / векторные операции. Вы могли бы реализовать такой класс даже обобщенно ( Point2D<T>где T может быть intили doubleили что - то еще).

Двухточечные / векторные операции настолько распространены для многих задач по программированию 2D-геометрии, что, по моему опыту, такое решение значительно повысит вероятность повторного использования существующих 2D-операций. Вы избежать необходимости повторных их реализации для каждого PixelCoordinate, GPSCoordinate«Whatsover» -координата класса снова и снова.

Док Браун
источник
1
вы также можете сделать это, struct PixelCoordinate:Point2D<int>если хотите правильную безопасность типов
ratchet freak
0

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

static GPSCoordinate getGPS(GPSCoordinate plane, Angle3D planeAngle, Angle3D gimbalAngle,
                        PixelCoordinate target, ZoomLevel zoom, PixelSize imageSize)

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

Это [более читабельно]. Это тоже хорошая идея.

Но имеет ли смысл создавать классы PixelCoordinate и PixelSize?

Если они представляют разные понятия, это имеет смысл (то есть «да»).

Или мне лучше использовать std :: pair для каждого.

Вы могли бы начать делать typedef std::pair<int,int> PixelCoordinate, PixelSize;. Таким образом, вы покрываете семантику (вы работаете с координатами и размерами пикселей, а не с std :: pair в вашем клиентском коде), и если вам нужно расширить, вы просто замените typedef классом, и ваш клиентский код должен требуют минимальных изменений.

И имеет ли смысл иметь класс ZoomLevel, или я должен просто использовать double?

Вы могли бы также определить его, но я буду использовать, doubleпока мне не понадобится связанная с ним функциональность, которая не существует для double (например, наличие ZoomLevel::defaultзначения потребует, чтобы вы определили класс, но пока у вас не будет ничего подобного что, держать его в два раза).

Моя интуиция в использовании классов для всего основана на этих предположениях [опущено для краткости]

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

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

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

Это хорошая идея использовать объекты для чего-либо, или у него есть недостатки, о которых я не знаю?

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

То есть, если у вас есть координаты, которые всегда имеют x, y и z, то гораздо лучше иметь coordinateкласс, чем передавать три параметра везде.

utnapistim
источник