Pull to refresh

Comments 20

Вместо:
 IdOf() : value_() {}

я бы сделал:
 IdOf() = delete;

Отсекаем ошибки программиста или проблемы с вниманием/памятью программиста — заставляем его обязательно задать идентификатор.

В статье Вы сразу приводите вариант с шаблонным классом, минуя вариант создания НЕ шаблонных классов идентификаторов. Не все поймут, почему именно шаблоны лучше, чем ветка наборов таких классов.
Отсекаем ошибки программиста или проблемы с вниманием/памятью программиста — заставляем его обязательно задать идентификатор.

Да, я тоже об этом думал. На мой взгляд, тут лучше полагаться на конкретные сценарии использования. Когда мы читаем объекты сущностей из внешних источников (строк ответа от бд, файла, и т.п.), то всё же бывает удобно иметь значения типов по-умолчанию. Правда, в реальной жизни заводят константу INVALID_ID или работают с boost::optional.

Не все поймут, почему именно шаблоны лучше, чем ветка наборов таких классов.

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

Ещё можно два конструктора объединить в один, впрочем это дело вкуса:
explicit IdOf(repr_type value = repr_type()) : value_(value) {}
Почему-то вспоминается венгерская нотация и microsoft'овское dwCounter и т.п.
Венгерская нотация сама по себе не поможет компилятору поймать логические ошибки, когда мы пытаемся получить виджет по id гаджета или что-то в этом роде.

Плюс, по моему субъективному мнению, app->addWidget(appWidgetId) лучше чем app->addWidget(widgetIdApp) или, тем более, app->addWidget(wIdApp).
Вопрос субъективного восприятия.
Поймать не поможет, но ты уже вряд ли случайно напишешь
dwAbc = bAbc

Я не говорю, что в.н. панацея, просто в свете темы — решение аналогичной проблемы.
Вполне можно написать bAaa = bBbb, а потом переименовать bAaa в dwAaa специальной тулзой, которая не читает венгерскую нотацию.
Нет, нельзя. Это некорректное действие — вы сами намеренно совершаете неправильное действие переименовывая переменную, используя неверный префикс.
Это все равно что руками «поправить» бинарный файл, а потом удивляться, что программа не работает.
Но концептуально действительно схоже, с тем отличием, что тут мы выносим семантическую разницу в компайл-тайм. У Спольски неплохая статья есть про то, как не путать строки со столбцами (с примечанием переводчика «а давайте это за нас ещё и компилятор проверит») и как венгерская нотация получила свою печальную репутацию.
О, спасибо за ссылку! J.S. неплохо пишет, часто его мнение отличается от «мейнстрима» и оказывается вполне хорошо обоснованным на мой взгляд.
Но, кстати, насчет System Hungarian он ошибся.
имеет смысл хеш функцию добавить в интерфейс.
В C++ хэш-функция, как правило, не входит в интерфейс класса. Она задаётся функциональным объектом, тип которого является параметром типа хэш-таблицы (иначе как задать хэш-функцию для примитивных типов?).
Для шаблона IdOf хэш-функцию можно задать, например, следующим образом:

#include <functional>

struct IdHasher {
    template <typename ModelType, typename ReprType>
    size_t operator()(const IdOf<ModelType, ReprType> &id) const {
        std::hash<ReprType> h;
        return h(id.value());
    }
};

// Usage example:

#include <unordered_map>

template <typename ModelType>
using IdToModel = std::unordered_map<IdOf<ModelType>, ModelType, IdHasher>;

Для числовых идентификаторов можно использовать enum class'ы.
#include <iostream>
#include <iomanip>
#include <climits>
using namespace std;

template<class Tag, class Base = int> struct def_enum
{
	enum class type : Base { }; // Base задаёт ёмкость типа; можно сделать через пару минимум-максимум
};

// тэги типов
struct X;
struct Y;

// искомые типы
typedef def_enum<X>::type XT;
typedef def_enum<Y>::type YT;

int main()
{
	XT x = XT(123), z = XT(456);
	YT y = YT(123);
	
	cout << (int)x << endl;
	cout << (int)y << endl;
	cout << boolalpha << (x==z) << endl;
	cout << boolalpha << ((int)x == (int)y) << endl;
	
//	cout << x << y << (x==z) << (x==y) << endl;
//	ошибки: нет подходящих операторов для (cout << x) и (x==y)
}

Да, неплохой вариант. Код в статье намеренно писался на C++03 по нескольким причинам.
Во-первых, этот стандарт знаком большему числу людей.
Во-вторых, так не создаётся впечатление, что для этого трюка непременно нужен C++11.
В-третьих, статья лежала в черновиках довольно давно, пока я не осознал связь этой чисто практической идеи с фантомными типами из мира ФП :)
Для 03 слишком много обвязки надо. Чтоб и EqualityComparable, и LessThanComparable, и хеширование прикрутить, и т.д.
К тому же, явно определённые конструкторы — формально ломают POD. Впрочем, это легко обойти, заменив конструкторы внешними функциями
template<class Tag> struct IdOf { int value; };
template<class Tag> IdOf<Tag> idof(int value) { IdOf<Tag> v = {value}; return v; }

....
IdOf<Widget> w;
....
w = idof<Widget>(123);


Ну и разница с хаскеллом ещё и в том, что newtype просто дурит мозг компилятору, равно как enum class, а класс-обёртка — это, всё-таки, data. (С энергичным полем).
К тому же, явно определённые конструкторы — формально ломают POD.
К слову, начиная с C++11, требования заметно ослабили: например, теперь можно и пользовательские конструкторы (главное, чтобы дефолтный конструктор, деструктор, а также конструкторы и операторы копирования и перемещения были тривиальными), члены данных не обязаны быть public (но все должны иметь одинаковую категорию видимости), разрешено наследование в ограниченном объеме (члены данных могут быть определены только в одном классе из иерархии).
Очень интересно, есть ссылка, где об этом можно почитать? Или только в стандарте?
Попробуйте тут почитать, возможно понравиться больше, чем стандарт, хотя описание все равно формальное.
Sign up to leave a comment.

Articles