Ни на что не претендую. И ни к чему не призываю.
Просто размышляю.
Я не собираюсь делать из C++ Delphi или наоборот. Всё написанное ниже представляет лишь "академический интерес".
Почему синтаксический сахар в виде парадигмы property не востребован в классическом C++?
Я вот совсем не сторонник холиваров. Я стремлюсь мыслить трезво.
И если у меня возникает вопрос - "почему такая-то синтаксическая конструкция востребована в языке A, но не востребована в языке B", то я пытаюсь найти ответ не в плоскости "религиозных войн", а в смысле различий между языком A и языком B.
Например меня долгое время мучил вопрос - "почему в C++ нет конструкции try..finally".
Но поскольку C++ разрабатывали более чем умные люди, то я сразу подозревал, что это "не просто так".
Этот вопрос хорошо освещён тут - Ссылка. Why doesn't C++ provide a "finally" construct?
Также меня долгое время мучил вопрос - "почему в C++ нету парадигмы property".
Особенно учитывая тот факт, что в расширениях C++ Builder'а эта парадигма - есть.
Но опять же - поскольку C++ разрабатывали более чем умные люди, то я подозревал, что это "не просто так".
И вот к каким выводам я не так давно пришёл.
Потому, что в C++ есть собственный "синтаксический сахар" в виде ссылок на значения, шаблонов и переопределения операторов.
Переопределения операторов "бездумно" - я не люблю, но при вдумчивой разработке библиотек - они очень и очень востребованы.
Итак.
Пусть у нас есть класс SomeClass на C++:
И его использование:
- ну тут всё понятно.
Есть - класс SomeClass.
У него есть член данных a - его можно писать и читать.
Теперь задумаемся о инкапсуляции данных.
Как "бедные" программисты на C++ (на взгляд Delphi программистов) обычно "вынуждены" писать?
Ну как-то так:
И его использование:
Сразу "бросается в глаза" копирование значения.
Это легко исправить, вернув ссылку (std::string &), а не значение (std::string):
Тут на сцену выходят ссылки. Они - тут главные.
Но пока на время забудем о них.
Немного потеоретизируем на тему "как было бы хорошо иметь property в C++".
Ну скажем так:
И его использование:
Красиво? Красиво.
Но зададимся вопросом - "зачем ?".
Вспомним про нашу триаду - ссылки, шаблоны, переопределения операторов.
А также вспомним о перегрузке функций.
Перепишем наш класс так:
И его использование:
Красиво? По-моему - тоже красиво.
Ну, а лишние скобочки - () это "не вопрос".
Всё?
Не совсем.
Встаёт вопрос.
А если мы хотим написать setter в виде:
- что тогда делать?
Ну во-первых тогда можно "поступиться красотой" и оставить метод-setter, а не метод возвращающий неконстантую ссылку.
Такая возможность у нас всегда есть:
И его использование:
- но как сделать "красиво"?
Тут вспомним о шаблонах и перегрузке операторов,
Напишем вспомогательный шаблонный класс ValueSetter:
И тогда наш класс приобретает вид:
И его использование:
Теперь - как параметризовать someCondition?
Приведу ссылку - std::sort.
Давайте немного перепишем класс ValueSetter (тут я могу путаться в синтаксисе, но надеюсь, что смысл понятен):
И наш класс приобретает вид:
А ещё ведь можно использовать лямбды.
C++11, передача Lambda-выражения как параметра.
Лямбда выражения теперь и в С++.
C++0x (С++11). Лямбда-выражения.
Могу что-то путать, но тогда наш ValueSetter приобретает вид:
И класс SomeClass приобретает вид:
Понятно, что "буков больше", чем при использовании парадигмы property, но я специально привожу всё как есть.
Чтобы было понятно "как оно работает".
Ведь далее связку m_a и const std::string a () с Setter_for_a & a () можно упаковать в ещё один шаблон.
И вынести все эти шаблоны в "стандартную библиотеку".
Было бы желание.
Ведь чем силён C++? Именно стандартными библиотеками и возможностью написания своих. С шаблонизацией. Те. мета-программированием. Т.е. с внесением "собственного синтаксического сахара".
Насчёт передачи функций и лямбд в шаблон - повторюсь, могу путать -посему критика на сей счёт - приветствуется. Пишу дома, где компилятора C++ - нет. Завтра на работе - проверю. Если будет время.
Ну и собственно есть практический вопрос к аудитории - как мне правильно написать шаблонный класс, который использует лямбду, которая использует один из параметров шаблона?
В этом вопросе - я пока "плаваю". Буду пытаться компилировать код.
Ну и коротко - если мы хотим "индексированные свойства", то что делать?
А всё просто:
И использование:
Ну и если на SomeClass1 определить оператор [] вот так:
- то можно написать и так:
Или даже так:
Ну а "всё остальное" - тоже через ValueSetter.
Вот собственно и всё пока.
Надеюсь, что кому-нибудь понравятся мои размышления.
А теперь об интересе не академическом, а вполне себе практическом.
В следующей заметке я напишу, как путём примерно такой же техники wraper'ов и holder'ов заменить использование std::string на NSString *, без перелопачивания "тонн кода".
Опять же лишь за счёт триады - ссылки, шаблоны, перегрузка операторов.
Спросите - зачем?
Отвечу ссылкой - Коротко. Про STL под iOS/MacOS.
Просто у нас было какое-то количество алгоритмов работающих с std::string и прочими объектами STL, которые "захотелось" "малой кровью" переделать на NSObject, NSArray, NSString, NSData и NSDistionary.
Ну не то, чтобы прям захотелось, просто "жизнь приказала".
Но на сегодня - пока всё.
P.S. Ну и в тему "синтаксического сахара" - Скорее "для себя". Классы в скриптовой машине.
P.P.S. http://programmingmindstream.blogspot.ru/2014/11/property-c.html?showComment=1417552354575#c8460879686358984674
"Я просто задался вопросом - "как я бы на месте Borland'а реализовывал бы __property не ломая компилятора и оставаясь в рамках ANSI стандарта". Только и всего :-)
В реальной жизни "именно это" я применять - никоим образом не собираюсь."
Просто размышляю.
Я не собираюсь делать из C++ Delphi или наоборот. Всё написанное ниже представляет лишь "академический интерес".
Почему синтаксический сахар в виде парадигмы property не востребован в классическом C++?
Я вот совсем не сторонник холиваров. Я стремлюсь мыслить трезво.
И если у меня возникает вопрос - "почему такая-то синтаксическая конструкция востребована в языке A, но не востребована в языке B", то я пытаюсь найти ответ не в плоскости "религиозных войн", а в смысле различий между языком A и языком B.
Например меня долгое время мучил вопрос - "почему в C++ нет конструкции try..finally".
Но поскольку C++ разрабатывали более чем умные люди, то я сразу подозревал, что это "не просто так".
Этот вопрос хорошо освещён тут - Ссылка. Why doesn't C++ provide a "finally" construct?
Также меня долгое время мучил вопрос - "почему в C++ нету парадигмы property".
Особенно учитывая тот факт, что в расширениях C++ Builder'а эта парадигма - есть.
Но опять же - поскольку C++ разрабатывали более чем умные люди, то я подозревал, что это "не просто так".
И вот к каким выводам я не так давно пришёл.
Потому, что в C++ есть собственный "синтаксический сахар" в виде ссылок на значения, шаблонов и переопределения операторов.
Переопределения операторов "бездумно" - я не люблю, но при вдумчивой разработке библиотек - они очень и очень востребованы.
Итак.
Пусть у нас есть класс SomeClass на C++:
class SomeClass { public: std::string a; }; // SomeClass
И его использование:
SomeClass X; std::string Y = X.a; X.a = "SomeString";
- ну тут всё понятно.
Есть - класс SomeClass.
У него есть член данных a - его можно писать и читать.
Теперь задумаемся о инкапсуляции данных.
Как "бедные" программисты на C++ (на взгляд Delphi программистов) обычно "вынуждены" писать?
Ну как-то так:
class SomeClass { private: std::string m_a; public: const std::string get_a () const { return m_a; } void set_a (const std::string & aValue) { m_a = aValue; } }; // SomeClass
И его использование:
SomeClass X; std::string Y = X.get_a(); X.set_a("SomeString");
Сразу "бросается в глаза" копирование значения.
Это легко исправить, вернув ссылку (std::string &), а не значение (std::string):
class SomeClass { private: std::string m_a; public: const std::string & get_a () const { return m_a; } void set_a (const std::string & aValue) { m_a = aValue; } }; // SomeClass
Тут на сцену выходят ссылки. Они - тут главные.
Но пока на время забудем о них.
Немного потеоретизируем на тему "как было бы хорошо иметь property в C++".
Ну скажем так:
class SomeClass { private: std::string m_a; private: const std::string get_a () const { return m_a; } void set_a (const std::string & aValue) { m_a = aValue; } public: property std::string a read get_a write set_a; }; // SomeClass
И его использование:
SomeClass X; std::string Y = X.a; X.a = "SomeString";
Красиво? Красиво.
Но зададимся вопросом - "зачем ?".
Вспомним про нашу триаду - ссылки, шаблоны, переопределения операторов.
А также вспомним о перегрузке функций.
Перепишем наш класс так:
class SomeClass { private: std::string m_a; public: const std::string a () const // - это getter { return m_a; } std::string & a () // - это setter { return m_a; } }; // SomeClass
И его использование:
SomeClass X; std::string Y = X.a(); X.a() = "SomeString";
Красиво? По-моему - тоже красиво.
Ну, а лишние скобочки - () это "не вопрос".
Всё?
Не совсем.
Встаёт вопрос.
А если мы хотим написать setter в виде:
void set_a (const std::string & aValue) { if (this->someCondition(aValue)) m_a = aValue; }
- что тогда делать?
Ну во-первых тогда можно "поступиться красотой" и оставить метод-setter, а не метод возвращающий неконстантую ссылку.
Такая возможность у нас всегда есть:
class SomeClass { private: std::string m_a; public: const std::string a () const // - это getter { return m_a; } void set_a (const std::string & aValue) // - это setter { if (this->someCondition(aValue)) m_a = aValue; } }; // SomeClass
И его использование:
SomeClass X; std::string Y = X.a(); X.set_a("SomeString");
- но как сделать "красиво"?
Тут вспомним о шаблонах и перегрузке операторов,
Напишем вспомогательный шаблонный класс ValueSetter:
template<class DataOwner, typename DataType> class ValueSetter { private: DataType & m_data; DataOwner * m_owner; public ValueSetter (DataOwner * aOwner, DataType & aData) : m_data(aData), m_owner(aOwner) // - инициализирующий конструктор { } ValueSetter (const ValueSetter & anOther) : m_data(anOther.m_data), m_owner(anOther.m_owner) // - копирующий конструктор { } ValueSetter & operator= (const DataType & aValue) // - оператор присваивания { if (m_owner->someCondition(aValue)) m_data = aValue; return *this; } } ; // ValueSetter ; //template
И тогда наш класс приобретает вид:
class SomeClass { private: std::string m_a; public: const std::string a () const // - это getter { return m_a; } typedef ValueSetter<SomeClass, std::string> Setter_for_a; Setter_for_a & a () // - это setter { Setter_for_a vSetter(this, a); return vSetter; } }; // SomeClass
И его использование:
SomeClass X; std::string Y = X.a(); X.a() = "SomeString";
Теперь - как параметризовать someCondition?
Приведу ссылку - std::sort.
Давайте немного перепишем класс ValueSetter (тут я могу путаться в синтаксисе, но надеюсь, что смысл понятен):
template<class DataOwner, typename DataType, typename Condition> class ValueSetter { private: DataType & m_data; DataOwner * m_owner; public ValueSetter (DataOwner * aOwner,DataType & aData) : m_data(aData), m_owner(aOwner) // - инициализирующий конструктор { } ValueSetter (const ValueSetter & anOther) : m_data(anOther.m_data), m_owner(anOther.m_owner) // - копирующий конструктор { } ValueSetter & operator= (const DataType & aValue) // - оператор присваивания { if (Condition(m_owner, aValue)) m_data = aValue; return *this; } } ; // ValueSetter ; //template
И наш класс приобретает вид:
class SomeClass { private: std::string m_a; static bool condition_for_a (const SomeClass & aThis, const std::string & aValue) { return aThis.someCondition(aValue); } public: const std::string a () const // - это getter { return m_a; } typedef ValueSetter<SomeClass, std::string, condition_for_a> Setter_for_a; Setter_for_a & a () // - это setter { Setter_for_a vSetter(this, a); return vSetter; } }; // SomeClass
А ещё ведь можно использовать лямбды.
C++11, передача Lambda-выражения как параметра.
Лямбда выражения теперь и в С++.
C++0x (С++11). Лямбда-выражения.
Могу что-то путать, но тогда наш ValueSetter приобретает вид:
template<typename DataType, typename Condition> class ValueSetter { private: DataType & m_data; public ValueSetter (DataType & aData) : m_data(aData) // - инициализирующий конструктор { } ValueSetter (const ValueSetter & anOther) : m_data(anOther.m_data) // - копирующий конструктор { } ValueSetter & operator= (const DataType & aValue) // - оператор присваивания { if (Condition(aValue)) m_data = aValue; return *this; } } ; // ValueSetter ; //template
И класс SomeClass приобретает вид:
class SomeClass { private: std::string m_a; public: const std::string a () const // - это getter { return m_a; } typedef ValueSetter<std::string, [&] (const std::string & aValue) ->bool { return this->someCondition(aValue); }> Setter_for_a; Setter_for_a & a () // - это setter { Setter_for_a vSetter(a); return vSetter; } }; // SomeClass
Понятно, что "буков больше", чем при использовании парадигмы property, но я специально привожу всё как есть.
Чтобы было понятно "как оно работает".
Ведь далее связку m_a и const std::string a () с Setter_for_a & a () можно упаковать в ещё один шаблон.
И вынести все эти шаблоны в "стандартную библиотеку".
Было бы желание.
Ведь чем силён C++? Именно стандартными библиотеками и возможностью написания своих. С шаблонизацией. Те. мета-программированием. Т.е. с внесением "собственного синтаксического сахара".
Насчёт передачи функций и лямбд в шаблон - повторюсь, могу путать -посему критика на сей счёт - приветствуется. Пишу дома, где компилятора C++ - нет. Завтра на работе - проверю. Если будет время.
Ну и собственно есть практический вопрос к аудитории - как мне правильно написать шаблонный класс, который использует лямбду, которая использует один из параметров шаблона?
В этом вопросе - я пока "плаваю". Буду пытаться компилировать код.
Ну и коротко - если мы хотим "индексированные свойства", то что делать?
А всё просто:
class SomeClass1 { private: std::vector<std::string> m_a; public: const std::string & a () const { return m_a; } std::string & a () { return m_a; } }; // SomeClass1
И использование:
SomeClass1 X; std::string Y = X.a()[0]; X.a()[1] = "Some Value";
Ну и если на SomeClass1 определить оператор [] вот так:
class SomeClass1 { private: std::vector<std::string> m_a; public: const std::string & a () const { return m_a; } std::string & a () { return m_a; } const std::string & operator[] (size_t anIndex) const { return m_a[anIndex]; } std::string & operator[] (size_t anIndex) { return m_a[anIndex]; } }; // SomeClass1
- то можно написать и так:
SomeClass1 X; std::string Y = X[0]; X[1] = "Some Value";
Или даже так:
SomeClass1 X; char Y = X[0][1]; X[1][0] = 'c';
Ну а "всё остальное" - тоже через ValueSetter.
Вот собственно и всё пока.
Надеюсь, что кому-нибудь понравятся мои размышления.
А теперь об интересе не академическом, а вполне себе практическом.
В следующей заметке я напишу, как путём примерно такой же техники wraper'ов и holder'ов заменить использование std::string на NSString *, без перелопачивания "тонн кода".
Опять же лишь за счёт триады - ссылки, шаблоны, перегрузка операторов.
Спросите - зачем?
Отвечу ссылкой - Коротко. Про STL под iOS/MacOS.
Просто у нас было какое-то количество алгоритмов работающих с std::string и прочими объектами STL, которые "захотелось" "малой кровью" переделать на NSObject, NSArray, NSString, NSData и NSDistionary.
Ну не то, чтобы прям захотелось, просто "жизнь приказала".
Но на сегодня - пока всё.
P.S. Ну и в тему "синтаксического сахара" - Скорее "для себя". Классы в скриптовой машине.
P.P.S. http://programmingmindstream.blogspot.ru/2014/11/property-c.html?showComment=1417552354575#c8460879686358984674
"Я просто задался вопросом - "как я бы на месте Borland'а реализовывал бы __property не ломая компилятора и оставаясь в рамках ANSI стандарта". Только и всего :-)
В реальной жизни "именно это" я применять - никоим образом не собираюсь."
тут должна быть картинка с буханкой хлеба, которую пытаются превратить в троллейбус. Да, но зачем?
ОтветитьУдалитьпроперти довольно спорный синтаксический сахар, в С++, как и в Java, принято использовать геттеры и сеттеры и никто не страдает по этому поводу. Многие считают что проперти в языке вообще лишние. А то что на шаблонах можно извращаться - так это всем давно известно. В общем - не нужно.
"Ну и собственно есть практический вопрос к аудитории - как мне правильно написать шаблонный класс, который использует лямбду, которая использует один из параметров шаблона?"
template
class SomeClass {
std::function myF;
...
};
или в качестве сигнатуры функции можно использовать другой параметр шаблона, не прописывая явно их взаимодействие на уровне декларации типов.
template
class SomeClass {
F myF;
...
};
Смешивание С++ и ObjC. В аду, должно быть, приготовлен отдельный котёл, для тех кто этим занимается.
скобочки в темплейтах похерились
Удалитьtemplate(typename T)
template(typename T, typename F)
соотвествтенно
"скобочки в темплейтах похерились"
УдалитьНу собственно как я в посте и написал :-) Спасибо.
Офигенскую книжку тут кстати прочитал. По мотивам разборок с шаблонами.
"Шаблоны C++. Справочник разработчика.Дэвид Вандервуд. Николаи М. Джосатис".
"или в качестве сигнатуры функции можно использовать другой параметр шаблона, не прописывая явно их взаимодействие на уровне декларации типов"
Удалить-- вот меня это несколько и смутило, что сигнатура явно не задаётся. Оказывается - действительно - не задаётся.
Есть понятие концепций, но его пока в стандарте C++ 11 нет. А жаль.
" В аду, должно быть, приготовлен отдельный котёл, для тех кто этим занимается."
УдалитьНу Джобс - уже наверное где-то там.
А "по делу" - не соглашусь. Есть "миллионы строк кода" написанные на C++ и их очень дорого переписать на Objective-C, да и глупо иногда. Да и производительность иногда просаживается. А вот "смешать" - самое то. На мой вкус.
"тут должна быть картинка с буханкой хлеба, которую пытаются превратить в троллейбус."
УдалитьКонечно :-) Я ровно так на это и смотрю.
" Да, но зачем? "
Я же написал - "чисто академический интерес".
Я просто задался вопросом - "как я бы на месте Borland'а реализовывал бы __property не ломая компилятора и оставаясь в рамках ANSI стандарта". Только и всего :-)
В реальной жизни "именно это" я применять - никоим образом не собираюсь.
«Этот вопрос хорошо освещён тут - Ссылка. Why doesn't C++ provide a "finally" construct?»
ОтветитьУдалить-- Этого не видел. Повеселило :-) Спасибо.
"Повеселило :-) Спасибо."
Удалить-- вы не согласны с автором? :-)
«вы не согласны с автором? :-)»
Удалить-- Конечно не согласен :-)
Если хотите, можем обсудить это в личке, поскольку тема - очевидный холливар.
Пишите. С удовольствием почитаю.
УдалитьХотя я лично с автором - согласен. Для Delphi finally - хорош. Для C++ - не хорош. Парадигмы разные.
Пишите.
Холивара кстати я в этом не вижу.
Ибо я же с самого начала написал - "задавался вопросом - почему нет". Потом написал - "прочитал автора и он меня убедил".
Т.е. я не "поверил в догму", а просто - "были сомнения", и "мне привели аргументы".
Посему совсем не холивар.
Просто - аргументы.
С интересом прочитаю ваши.
Может ваши - перевесят. А может и нет.
Хотя почему лично? :-) Потом хоть можно будет опубликовать?
Я прочитал ваше письмо. Меня даже как "закоренелого дельфиста" - не убедили. И не только меня.
УдалитьА может все проще, сахар property вводился только для публикации свойств в инспекторе объектов, нужно же было как то разделять свойства редактируемые визуально от невизуальных Понятно что без регистрации класса в IDE property нафиг не нужен.
ОтветитьУдалитьТут вы правы. Но и это на C++ можно сделать по-другому.
Удалить