Pull to refresh

Comments 86

Строгая типизация и ограничение работы с памятью/указателями — где-то я такое уже слышал )
Главная миссия именованных параметров — исключить ошибки при вызове функций и повысить читабельность кода — осуществляется.
Если сужать миссию именованных параметров до такой — то выполняется. Но мне кажется что опциональность параметров — очень важная возможность. И её пользовательскими типами не решить. Иначе придётся либо писать функции со множеством параметров, либо всё равно городить костыли описанные по ссылкам в начале вашей статьи.
Тоже можно решить, по-моему. Трудно, конечно, говорить в общем случае, но параметры, передаваемые в функцию обычно как-то логически связаны. А значит их можно объединить в один тип (или несколько). После этого можно инициализировать только нужные поля объекта этого типа и передавать в подпрограмму. Да, вызов в одну строчку может потеряться, но это не самая страшная потеря, особенно если ее ценой получится увернуться от тяжелых костылей.
В плюсах же есть возможность указывать аргументу значение по умолчанию или речь о чем-то другом идет?
Да, но нельзя сделать так
CreateOptions(2, 3, Default, 8);
Заметив больше чем 2-3 параметра функции, я обычно начинаю задумываться " А все ли я делаю правильно ?". Потом либо как-то разбиваю функцию, либо создаю структурку типа params для функции. Да и перемешивание и пропуски параметров вносят какую-то бардачность в код. Это лично мое ИМХО не претендующее на истинность.
Не совсем функция, но для примера сойдет. Скажем, когда мы пишем
std::unordered_set<std::string>
после подстановки параметров по умолчанию получается:
std::unordered_set<std::string, std::hash<std::string>, std::equal_to<std::string>,
    std::allocator<std::string>>

Если мы хотим создать эту же структуру только с нашим аллокатором (поменять последний параметр), то нам прийдется явно выписывать все эти параметры:
std::unordered_set<std::string, std::hash<std::string>, std::equal_to<std::string>,
    my_allocator<std::string>>
вместо какой-нибудь такой записи:
std::unordered_set<std::string, Allocator : my_allocator<std::string>>
да, но так как все параметры позиционные, а не именные нельзя пропустить несколько, а после них несколько задать.
Т.е.
void foo(int *ptr = NULL, int int_arg = 5);

int glob_int = 5;

foo(); // ОК эквивалентно foo(NULL,5)
foo(&glob_int); // OK, эквивалентно foo(&glob_int, 5)
foo(13); // BAD - мы попытались привести int к указателю, вместо вызова foo(NULL, 13)

А если опциональному позиционному параметру задать дефолтный конструктор (без параметра), то проблема остается?
Да. А даже если бы это было не так, то решение работало бы только в случае параметров разных типов, что тоже не очень хорошо.
Кажется, что если что-то не устраивает в языке для конкретной задачи — просто поменяйте язык. Вариантов много. Если возможности поменять нет (легаси и всё такое) — просто постарайтесь принять это как данное, а для другой задачи выберите что-то более подходящее. Воистину: если не можете изменить ситуацию — измените отношение к ней.
Возможности языка хороши когда это чистые родные возможности языка, а не искусственные костыли:)
А что делает создание кастомного типа в ООП костылём? Это какбы и есть родная возможность языка, разве нет?
Если вы имели в виду не конкретный пример из статьи — прошу извинить, неправильно понял.
Я не про кастомные типы конечно, а про то с чего начал автор статьи (эмуляция именованных параметров, по ссылкам в начале статьи)
Скажем, да, в Луа можно присваивать значения сразу нескольким переменным
Да и в плюсах обычно можно:
x = y = getPosition();
Нельзя задавать одно начальное значение для нескольких переменных при конструировании.
Я неудачно описал этот пример. Имелось в виду присвоение двух разных значений. Т.е. там еще функции не ограничены одним возвращаемым значением.
Для плюсов:
std::tie(x, y) = getPosition();
Да, менять местами параметры такой подход не позволит, но это не так и важно.

Почему же? Добавим немного С++ 11:

struct width {
  explicit width(int val) : v{val} {}
  int v;
};

struct height {
  explicit height(int val) : v{val} {}
  int v;
};

template <typename... T> int getVolume(T... args) {
  auto t = make_tuple(args...);

  int w = get_element_by_type<width>(t).v;
  int h = get_element_by_type<height>(t).v;

  return w * h * h;
}

int _tmain(int argc, _TCHAR *argv[]) {
  cout << getVolume(height(2), width(1)) << endl;
  cout << getVolume(width(1), height(2)) << endl;

  return 0;
}


Функция get_element_by_type возвращает элемент кортежа по его типу. В С++ 14 функция std::get ведёт себя аналогично.

get_element_by_type
namespace detail {
template <class T, std::size_t N, class... Args>
struct get_number_of_element_from_tuple_by_type_impl {
  static constexpr auto value = N;
};

template <class T, std::size_t N, class... Args>
struct get_number_of_element_from_tuple_by_type_impl<T, N, T, Args...> {
  static constexpr auto value = N;
};

template <class T, std::size_t N, class U, class... Args>
struct get_number_of_element_from_tuple_by_type_impl<T, N, U, Args...> {
  static constexpr auto value =
      get_number_of_element_from_tuple_by_type_impl<T, N + 1, Args...>::value;
};

} // namespace detail

template <class T, class... Args>
T get_element_by_type(const std::tuple<Args...> &t) {
  return std::get<detail::get_number_of_element_from_tuple_by_type_impl<
      T, 0, Args...>::value>(t);
}
Мне вот интересно: А это всё compile-time решения? Т.е. не разрастётся ли скомпилированный код от этих наворотов. Второй вопрос — обёртывание базового типа int в структуру с методом добавит нам указатели на методы или нет?
Я не специалист в C++, но мне кажется что вся эта обвеска сильно утяжелит передачу параметров (больше места в стеке) и т.д.
всё в компайлтайме развернется, не бойтесь. Естественно если врубить оптимизацию. Хотя на деле можно было вообще без тупла обойтись и собиралось бы быстрее.
Таки да, можно обойтись без кортежа. Также можно добавить поддержку параметров по умолчанию.

Как-то так:
#include "stdafx.h"
#include <iostream>
using namespace std;

namespace detail {
template <typename T, typename H, typename... Args>
typename enable_if<!is_same<T, H>::value, T>::type
get_by_type_impl(H head, Args... tail) { return get_by_type_impl<T>(tail...); }

template <typename T, typename... Args>
T get_by_type_impl(T head, Args... tail) { return head; }

template <typename T, typename... Args> T get_by_type_impl() { return T(); }
} // namespace detail

template <typename T, typename... Args> T get_arg(Args... tail) {
  return detail::get_by_type_impl<T>(tail...);
}

template <typename T> struct arg {
  explicit arg(T val) : v{val} {}
  T v;
  operator T() { return v; }
};

struct width : arg<int> {
  using arg::arg;
};

struct height : arg<int> {
  explicit height(int i = 42) : arg(i) {}
};

template <typename... T> int getVolume(T... args) {
  int w = get_arg<width>(args...);
  int h = get_arg<height>(args...);

  return w * h * h;
}

int _tmain(int argc, _TCHAR *argv[]) {
  cout << getVolume(width(10)) << endl;
  cout << getVolume(height(20), width(1)) << endl;
  cout << getVolume(width(1), height(2)) << endl;

  return 0;
}


PS. Я не гуру шаблонного метапрограммирования, так что мог что-то не учесть в данном примере.
Можно и без с++11 решить вполне поставленную задачу про перемену мест слагаемых в дате:

class Date
{
public:
    void set(const Day& d);
    void set(const Month& m);
    void set(const Year& y);
};


template<class D, class M, class Y>
Date createDate(const D& d, const M& m, const Y& y)
{
    Date date;
    date.set(d);
    date.set(m);
    date.set(y);

    return date;
}

//теперь можно вызывать
createDate(Day(3), Year(4), Month(55));
createDate(Year(4), Month(55), Day(3));
createDate(Month(55), Day(3), Year(4));
Это, кстати, поинтересней вышеприведенного решения, т.к. здесь не требуется, чтобы параметры были точно типов Day, Year и Month, а достаточно, чтобы можно было однозначно конвертировать каждый аргумент в один из этих типов. Надо бы только защититься от подобных вызовов:
createDate(Day(3), Day(3), Day(3));
И это решаемо:
#include <type_traits>

template<class D, class M, class Y>
Date createDate(const D& d, const M& m, const Y& y)
{
    static_assert(!std::is_same<D, M>::value, "types are equal");
    static_assert(!std::is_same<D, Y>::value, "types are equal");
    static_assert(!std::is_same<M, Y>::value, "types are equal");
    Date date;
    date.set(d);
    date.set(m);
    date.set(y);

    return date;
}
Это не решает проблему до конца. Пусть у нас есть какой-нибудь тип, который может быть преобразован, скажем, в Day:
struct T {
    operator Day() const;
};

Тогда мы можем делать так:
createDate(T{}, Year(4), Month(55));
и это реально здорово и является преимуществом вашего метода перед предложенным выше.

Но мы cможем сделать и вот так:
createDate(T{}, Day(4), Month(55));
поэтому до конца проблема не решена.

P.S. И это уже не «без С++11» ;-)
Можно написать простенький CanCast для этого
template<class TO, class FROM>
struct CanCast
{
	static char Can(TO*);
	static char Can(TO);
	static long Can(...);

	enum
	{
		Result = sizeof(Can(*(FROM*)0)) == sizeof(char) || sizeof(Can((FROM*)0)) == sizeof(char),
	};
};

и всё будет хорошо.
Т.е. не разрастётся ли скомпилированный код от этих наворотов.
Нет, компилятор соптимизирует весь этот код до печати двух четвёрок, можете в этом сами убедиться, посмотрев ассемблерный код (я использовал printf() для печати, чтобы листинг был покороче и попонятней). Хотя теоретически в этом коде куча дополнительных вызовов мелких вспомогательных функций (можете поставить опцию "-O0" вместо "-O2" и сами посмотреть), весь дизайн С++ построен на том, что компилятор сумеет такие вызовы заинлайнить и соптимизировать получившийся код.
Второй вопрос — обёртывание базового типа int в структуру с методом добавит нам указатели на методы или нет?
Нет, указатели добавляют только виртуальные методы, эти структуры в точности совпадают с сишной структурой с одним int'ом внутри.
В какое удивительное время мы живем…
так он соптимизировал ваш конкретно код в котором весь этот обвес не нужен ибо начальные и конечные данные никак не меняются. Соответвено он просто выбросил все оставив только необходимое. А если с данными внутри созданной структуры поработать извнне то думаю весь обвес останется.
Не уверен, что я правильно вас понял, но в данном примере обвес строится вокруг функции createDate(), а не каких-то структур данных, а типы width и height, если вы о них, — это обычные сишные структуры с одним интом внутри, которые, в свою очередь, на машинном уровне то же самое, что и простой int.
Мне кажется, или теперь метод getVolume принимает вообще любой тип данных?
А поддерживал бы С++ инициализацию в стиле С99, можно было бы написать так:
typedef struct Date {
    int day;
    int month;
    int year;
} Date;

void f(Date d);
...
    f((Date){.day = 1, .month = 2, .year = 3});
Это, конечно, в стиле Java, но если вы действительно часто встречаетесь с такой проблемой в разных сложных случаях — стоит попробовать шаблон проектирования Builder:

Date date = Date.getBuilder().setDay(3).setMonth("Jan").setYear(1999).build();
Вообще подход «Текучий интерфейс» как таковой и переход от функций к функциональным объектам это неплохая альтернатива именованным параметрам (Fluent_interface). Да только вот объявление всего этого гораздо многословнее выходит
Нет, в c++ приходится писать что-то вроде setArmy(-1, -1, -1, 42) вместо православного setArmy(solderTypeThree = 42). Тут именованные параметры пригодились бы. Я уж и не говорю про пары getX/getY вместо getPos в каждом втором граф движке. А, c++, я напомню, стандарт де-факто гдля геймдева.
Если так необходимо сохранить читаемость кода, не лучше и проще ли для тех волшебных значений объявить переменные/константы с соответствующими именами, и их уже отдать в функцию?
Вы давайте тут своими простыми решениями не пудрите нам мозги, у нас тут C++!
Проблема надуманна.
Всегда можно воспользоваться инструментами IDE и получить подсказку, какое имя у параметра, которые в данный момент заполняется.
Разве что вы пишете C++ код в блокноте. Но это не проблем С++ и отсутствия именования переменных.

Вообще, решение отличное! Но только проблемы нету.

someMethod(user1, user2);

Если же объекты неравнозначны, например, метод ставит в подчинение user2 объекту user1, то совсем нелишними будут специальные типы, отражающие роли объектов.

Совсем не лишним будет ООП
user1->someMethod(user2);
UFO just landed and posted this here
Не согласен. Ситуации, когда из метода не понятно что у него за параметры — крайне редка.
Давайте уж говорит откровенно, это — говнокод:
Date theDate = createDate(2, 3, 4);

В реале будет что-то вроде:
Date theDate = createDate(currentYear, currentMonth, currentDay);

Или что-то аналогичное. Цифры вообще в коде встречаются крайне редко. В 99% случаев вместо цифр будет либо константа, либо переменная.
А вот такое:
Date theDate = createDate(Year(currentYear), Month(currentMonth), Day(currentDay));

Читабельности не помогает ну вообще никак, и даже наоборот.

Вот пример типичного(ИМХО) кода. Согласитесь, без именования параметров в каждой строке понятно что передается и зачем:
cLabel* Label = InitWindow(cLabel,panel);
Label->setLeft(0);
Label->setTop(0);
Label->setWidth(panel->width());
Label->setHeight(panel->height());
Label->setVAlign(VA_CENTER);
Label->setHAlign(HA_CENTER);
Label->setCaption(Text);
Label->setColor(cColorf::WHITE);
Label->setFont(«Roboto-Regular.ttf»,fontSize);

Даже не типичный макрос InitWindow разгадывается без особых проблем.
Если хочется «короче» — про builder здесь уже говорили.

По сути почти все примеры, в которых наименование действительно нужно будут весьма ограничены и createDate — чуть ли не самый лучший из них.
UFO just landed and posted this here
А типы то как помогут?

есть некий наследуемый метод объявленный традиционно:
virtual void classA::createDate(Year year, Month month, Day Day);

и наследники его во всю наследуют:
virtual void classB::createDate(Year year, Month month, Day Day);


Приходит тим лид, и в классе родители параметры меняются местами:
virtual void classA::createDate(Month month, Day Day, Year year);

а в наследниках мы изменить забыли:
virtual void classB::createDate(Year year, Month month, Day Day);

Разве поможет здесь именование параметров?
UFO just landed and posted this here
Это вроде бы С++11, которого много где еще нет.
UFO just landed and posted this here
Windows Compact 7. Вполне себе актуальная ОС в Enterprise секторе. Разработка только на 2008 студии, например.
Многие проекты компилируются специально на старых сборках компиляторов, чтобы не дай бог чего не сломалось.
Плюс большую часть готового кода никто не будет переводить под новые стандарты.
Так то я с вами согласен, override прекрасно решает эту проблему. Если только нет выше перечисленного.
UFO just landed and posted this here
Вы меня заставили задуматься.
Ведь ничего не мешает при сборке под 2008 студию объявить дефайны
#define override
#define final
и в итоге код написанный с учетом этих директив соберется и под старыми компиляторами. При этом под новыми компиляторами можно будет пользоваться полными возможностями компилятора для отлова ошибок этого вида.
UFO just landed and posted this here
Если в вашей области C++11 всеми поддерживается, то это не значит, что он поддерживается везде. Возьмите любой embedded или, хотя бы, тот же Android, и увидите, что ваша область — скорее исключение, чем правило.Clang вообще поддерживается только на некоторых Unix системах, почему вы тогда не рассмотрите тот же MSVC? Мыслите шире.
Причем final ещё и оптимизировать помогает, скажем, в таком коде:
struct A { virtual void f(); }
struct B : public A { virtual void f() override final; }
struct C final : public A { virtual void f() override; }

void do_with_B(B& b) { b.f(); }
void do_with_C(C& c) { c.f(); }
в обоих функциях do_with... вызов f() будет вызвана напрямую соответствующая функция, тогда как без final там был бы вызов через таблицу виртуальных функций.
Менять порядок аргументов должна IDE, которая сама найдёт все вызовы, и переставит аргументы соответствующим образов.
Почему сразу говнокод. Просто очищеный от всего лишнего пример. Именованные переменные вообще не помогут, потому что в коде может оказаться и так

Date theDate = createDate(currentYear, currentMonth, currentDay);

и так

Date theDate = createDate(currentDay, currentMonth, currentYear);

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

Date theDate = createDate(currentDay, currentDay, currentYear);

Все это легко пропустить при чтении кода, да и компилятор доволен. Речь ведь не о том, как «разгадывать» сигнатуры функций, а как обезопасить себя от потенциальных ошибок, переложив максимум рутины на компилятор.
Ну ведь это и не спасет от ошибок.
Просто будет написано вот так:
Date theDate = createDate(Day(currentDay), Month(currentDay), Year(currentYear));
UFO just landed and posted this here
Date theDate = createDate(Year(currentYear), Month(currentMonth), Day(currentDay));

Лол. Переменная currentYear должна быть типа Year, переменная currentMonth типа Month. И тогда оп-па и код нормально выглядит. Я не знаком со спецификой разработки на C++, но создание экземпляров таких спец.типов с легкостью локализируется в небольшом количестве кода, а дальше — уже просто тупая передача данных между методами. Так что ваш аргумент принимается, но нивелируется.
Увы. Идея «а давайте обернём примитивные типы» не работает.
Оно конечно можно пару тройку ключевых типов обернуть чем-то вроде:
#define BOOST_STRONG_TYPEDEF()
Но это не айс. Правильная эмуляция примитивного int к, примеру, не примитивная задача.
Класс это класс, даже если притворяется примитивным типом.
Как минимум, после компиляции, в релизе и отладке эти ухищрения лучше обратно заменить на нативные типы.

т.е. хотелось бы строгих типов но нету.

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

остановился на компромиссе:
typedef яблоки int;
typedef груши int;

груши f = 1;
яблоки a= 2;
vector<груши> g;

С C++11 можно сделать так:
enum class apples : unsigned int {};
apples будет отдельным типом (в него нельзя будет присвоить int или груши, скажем), при этом по умолчанию для него будут определены все операторы сравнения и функция хеширования (поэтому, например, его можно положить в любой STL контейнер, ничего дополнительно не дописывая). Из минусов: не работают арифметические операции, не работает вывод через потоки (и то, и то можно ручками дописать) плюс проблемы с синтаксисом конструирования, т.к. единственный способ сконструировать такой тип из встроенного числового типа — это сделать явный каст:
apples a(0); // Compilation error
apples b( apples(0) ); // OK
apples c = apples{0}; // Compilation error
apples d = apples(0); // OK
можно. для хендлов будет отлично.
Из минусов: не работают арифметические операции

это не минус — это приговор, для арифметических типов.
«Не работают» значит «не работают сразу из коробки». Но ручками все реализуемо:
apples operator+(apples a, apples b)
{
    return apples(unsigned(a) + unsigned(b));
}
А надо ли много копипастить? Например, есть ключ сущности постов (PostKey: EntityKey) или ключ сущности комментариев (CommentKey: EntityKey). В итоге нужно научить корректно с этим работать десериализаторы/сериализаторы, дебагер, биндеры (это специфично для веба — штуки, которые умееют из данных http-запроса строить специфичные модели, чтобы не работать с просто строками). + Научить операциям неявного приведения int -> PostKey; явного приведения PostKey -> int; определить корректные сравнения на равность. Создание экземпляров таких сущностей локализуются в каких-то определенных местах; явное использование сырых значений тоже локализуется в каких-то определенных местах.

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

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

Рекомендую присмотреться внимательно к данному подходу. Именованые параметры, кстати, эту проблему решают не во всех местах.
Но есть некоторые товарищи,

не подскажете ссылочку для ознакомения?

достаточно найти парочку основных сущностей

Это реально. Я поискал в своих проектах:
Заголовок спойлера
/**внутренний глобальный идентификатор клиента*/
#ifdef _DEBUG
BOOST_STRONG_TYPEDEF(DWORD, client_handle_type);
BOOST_CLASS_IMPLEMENTATION(client_handle_type, primitive_type)
namespace boost 
{
    template <>
    struct is_integral<client_handle_type> : public ::boost::true_type {};
    template <>
    struct is_unsigned<client_handle_type> : public ::boost::true_type {};
    template <>
    struct is_signed<client_handle_type> : public ::boost::false_type {};
    template <>
    struct is_arithmetic<client_handle_type> : public ::boost::true_type {};
    template <>
    inline ::std::string lexical_cast<::std::string, client_handle_type>(const client_handle_type & arg)
    {
        return lexical_cast<::std::string, DWORD>((DWORD)arg);
    }
}
#else
typedef DWORD  client_handle_type;
#endif


На парочку троечку подобных обёрток меня хватило
Погодите. Как альтернатива — можно использовать паттерн Builder с Fluent Interface. Но быть может это оверкил/обходной путь, т.к задача Builder совсем другая. Но все же. Что вы думаете?
О, прошу прощения, только заметил комментарий выше на эту тему.
По моему, озвученная проблема имеет место, но ее решение крайне переусложнено. На мой скромный взгляд, конструкция вида
Date theDate = createDate(2, 3, 4);

вообще не должна появляться в коде. В худшем случае это должно выглядеть примерно так:
const int kDay = 2;
const int kYear = 3;
const int kMonth = 4;
Date theDate = createDate(kDay, kYear, kMonth);
Имя — это совсем не произвольная строка. Хотя бы потому, что выбирается из заранее известного множества. Еще оно, скажем, не содержит цифр (хотя тут я не уверен).

Кстати, чудовищное заблуждением. Именем может быть практически любая последовательность Юникод символов. Фамилией — тоже. К тому же понятие фамилия не во всех культурах существует.
Что-то мне кажется, все предложенные варианты по добавлению функциональности «именованные параметры в C++» — это оверкиллы. Если параметров много и действительно можно запутаться в том, какое значение какому параметру предоставлять или есть еще куча каких-то optional-параметров, то можно воспользоваться Builder + Fluent interface. Ели параметров мало, но все еще можно запутаться, то быть может имеет смысл документировать метод относительно его параметров.
Насчет интернациональности команды — если разработчик видит выражение:

Date date = new Date(1, 2, 3);

то он скорее всего непременно обратится к документации, так как знает, что если результат выполнения некоторого кода можно трактовать двояко, то лучше обратиться к документации. Там все аспекты работы этого кода должны быть описаны.
Очень крутая парадигма.
И знаете где её очень не хватает? В OpenCV! Там часто есть риск перепутать координаты, когда обращаемся к элементам матрицы: где-то (row, col), где-то (x, y) — путаница!
Веселуха начинается, когда на сцене появляется физика.
Ну типа там паскали это ньютоны на метр квадратный. И вам нужно описать весь набор допустимых арифметических операций, ну типа

Pascals operator/ (Newtons force, Squaremeters square) {return Pascals(force.m_value/square.m_value)}


ЧСХ, сами физики обычно кладут на типобезопасность болт и обезразмеривают все величины. И потом с чистой совестью складывают безразмерные длины с безразмерными температурами.
UFO just landed and posted this here
Хотел написать этот коммент. Для инженера boost::units просто супер. Куча ошибок и опечаток стала вылезать во время компиляции. Хотя нужно привыкнуть.
Кстати, в F# компилятор умеет работать с единицами измерения. Например:

[<Measure>] type m
[<Measure>] type s

let distance = 100<m>
let time = 5<s>
let speed = distance / time // Размерность будет m/s

Правда, этот подход не убережет от странных делений, типа «доллар на секунду в квадрате», но зато не даст случайно сложить или сравнить килограммы с галлонами.
UFO just landed and posted this here
Скорее производная от изменения цены. В финансовых приложениях, возможно, и такое бывает. Я просто хотел показать пример маловероятной размерности.
> Тут проще создать тип Position и присваивать значение его объекту

И сколько ж таких типов выйдет, интересно, скажем в проекте на миллион строк? Тысяча? Десять тысяч?
Если над проектом работает более одного человека — к гадалке не ходи, в разных концах проекта будут заведены разные типы для одного и того же. Вот веселье-то.
А ведь их все ещё продокументировать надо. Сколько страниц будет в документации к проекту?

Чудес в мире не бывает. За всё надо платить.
Каких таких? Да, всегда можно выкрутиться и не заводить новый тип. Но в проекте на миллион строк с более чем одним разработчиком, которые творят что хотят, каждый выкрутится по-своему… Даже разнообразие вариантов в комментариях к статье намекает. И документации это добавит не только объема, но и разнообразия.
Если вы имели в виду «таких простых», так это потому что пример. Даже банальная позиция со временем обрастает всякими полезными методами типа серилизации, отладочного вывода, проверки корректности. В проектах до сотни тысяч строк не разу не видел, чтобы какой-то тип висел просто сборником полей без дополнительной логики, которую иначе пришлось бы копипастить в сотни мест…
Самое время привести основную мысль статьи. Примитивным стандартным типам не место в интерфейсах реальной системы. Такие типы — просто строительные блоки, из которых нужно именно строить, а не пытаться жить внутри.

Да, и в Аде с этим (созданием пользовательских типов на базе примитивных типов) все значительно лучше чем в С++, поэтому они там повсеместно используются, и базовых типов обычно действительно в интерфейсах пакетов не видно. Просто потому, что создать свой тип — это одна-две строчки. Ну и вообще, не принято там использовать базовый Integer какой-нибудь.

И это то, чего в С++ действительно не хватает. Хотя можно конечно извратиться, и попробовать сделать аналог на шаблонах. Будет более громоздко конечно, но хоть как-то.
Редко комментирую посты, но просто не могу сдержаться. Я иногда чувствую себя инопланетянином.

Чтобы не спутать порядок параметров при вызове функции вы готовы написать три новых класса? Правда?!… Серьезно?!!! Вы собираетесь это делать для каждой функции с однотипными параметрами?

Вы понимаете, зачем нужен язык программирования — он нужен для того, чтобы точно и УДОБНО выражать мысли программиста. Если для такой простой задачи (которая в С++, кстати говоря, обычно решается изменением названия функции на createDate_DayMonthYear), если вместо этого вам нужно создавать три новых класса — это НЕ УДОБНО. Это ОЧЕНЬ ПЛОХО. Это грубое нарушение принципа KISS. В том числе потому, что вероятность ошибки при написании 3 новых классов и модификации исходной функции createDate в разы выше, чем вероятность спутать порядок параметров.
Чего ж инопланетянином. Как раз описанные вами подходы более распространены, по крайней мере, по моим наблюдениям. А по поводу KISS — это когда как. Если createDate используется нечасто, то, возможно, создание специальных типов будет перегибом. Проще назвать createDate_DayMonthYear и проверять корректность вручную. Хотя я тут ничего, кроме лабораторной работы, предствить не могу. В противном случае проверку можно переложить на компилятор, сэкономив кучу времени и нервов.
В том числе потому, что вероятность ошибки при написании 3 новых классов
Нам же не с нуля надо эти классы разрабатывать с какой-то уникальной функциональностью в каждом, они весьма однотипны. Всё можно устроить так, что добавление нового класса будет требовать от нас написание одной строчки кода (например, завернуть объявление класса в макрос) или одного клика в IDE для вставки соответствующего сниппета.
> createDate_DayMonthYear

createPerson_FirstName_LastName_DateOfBirthDayMonthYear_Gender()
Sign up to leave a comment.

Articles