Comments 91
U.setEnergy(V.getRho() * sqr(V.getV) / 2.0);
то ли дело
U.energy = V.rho * sqr(V.v) / 2.0; — просто и понятно.
А в реальном коде выражения гораздо развесистее, особенно когда в одной задаче и уравнения Максвелла и Навье-Стокса.
По началу тоже хотел использовать property примерно как в этом топике описано, но из-за того, что поддерживать неудобно, просто отказался от этой идеи и стал использовать поля c public доступом.
Система свойств есть, но заточена она по большей части под динамическое использование через мета-объектную систему.
__declspec( property( get=get_func_name ) )
в конкретном компиляторе (прощай переносимость) мне совсем не смешно.Это я к тому, что в Смолтоке пропертей нет и быть не может, но почему-то никто от этого не страдает.
#include
#define PROPERTY(TYPE, NAME) \
public: TYPE get_##NAME() const { return property_##NAME##_; } \
public: void set_##NAME(const TYPE& NAME) { property_##NAME##_ = NAME; } \
private: TYPE property_##NAME##_
class A {
PROPERTY(int, a);
};
int main() {
A a;
a.set_a(5);
::std::cout
Потерялся: инклюд иострим и вывод get_a() в ::std::out.
В Visual C++ есть свои свойства к примеру.
IList list;
for (int i = 0, count = list.Count; i < count; i++)
{
}
когда я вижу код вида a.b = c в С++ я по умолчанию считаю, что b это переменная,
В нормально используемом C++ в 99% времени у вас нет доступа к полям напрямую (это же не Си), а только через методы.
т.е. оверхед от обращения к ней минимален и я могу к ней обращаться таким образом сотни раз к примеру, а тут вдруг оказывается, что там каждый раз могут производится какие-то вычисления, которые могу быть отнюдь не быстрыми.
Если бы в С++ были проперти наряду с обычными функциями, то никто бы не запрещал для "тяжелых" функций не использовать проперти, а оставлять из функциями. Лучше, когда выбор есть, чем когда его вообще нет
С другой стороны, это мешает применять свойства как я описал комментарием выше;
Очень интересный способ потратить 90% времени не на сам проект, а на обустройство удобств дальнейшего программирования.
Скрывается разбиение на 2 метода доступа к данным, делая обращение к ним более наглядным.
Сама концепция удобна, если поддерживается языком, а вот приведенные костыльные реализации для не поддерживающих это языков — сомнительны в плане своей оправданности.
Для плюсов такое поведение неестественно, это и смущает.
Наоборот крайне нежелательно давать прямой доступ к полям класса. Лучше, чтобы к полям был доступ через сокрытые setter/getter в виде проперти.
К тому же, с точки зрения терминологии, методы — это поведение класса, а на них вешают еще и запись/чтение полей класса. По мне так лучше, когда методы — это методы, а свойства(поля/проперти) — это свойства.
И на одно проперти приходится один путь доступа (одно имя) и на запись и на чтение.
1. Транзакции и валидацию. (если хотя бы одно свойство не удалось изменить по каким-либо причинам откатывается вся транзакция).
2. Транзакции должны уметь склеиваться. При этом выкидываются промежуточные значения (если таковые имеются). Это позволяет, например, удалять из UNDO изменения, экономя таким образом память, но оставляя возможность вернуться на много действий назад.
3. Свойства должны сами разбираться с типами. Преобразования типа string -> int должны выполняться автоматически.
4. Должна быть система ивентов, позволяющая навешивать обработчики на изменение свойств любых объектов. Например, «перерисовать окно, если отображаемое свойство изменилось».
5. Свойства и транзакции должны уметь сериализоваться. В этом случае другую сериализацию можно не писать в принципе (если все хранится в свойствах).
6. Любое изменение должно обязательно проходить валидацию. Причем, система валидации должна позволять ставить обработчики на валидацию свойств, которые могут выполнять дополнительные проверки и кидать исключения.
7. Свойства должны поддерживать контейнеры. Т.е. давать возможность писать что-то в духе std::for_each и т.п., но при этом все изменения должны попадать в транзакцию.
8. Использование свойств должно быть простым.
10. Также бывает крайне полезной возможность получать доступ к свойствам, через такие вещи как COM.
Вот как-то так, а иначе это детский лепет. Зачем нужны свойства без транзакций, я вообще лично не понимаю.
p.s. И да, это все реализуемо на С++.
Property это не то, что используется внутри логики, там они не нужны и вредны (к тому же, они намного медленнее). Они используются для связи логики с внешним миром:
Примеры:
1. Undo/Redo
Когда у вас все на свойствах, вам вообще не нужно думать как оно работает. Когда пользователь из интерфейса чего-то меняет, меняются свойства. Каждый «apply» пишется в отдельную транзакцию (на уровне свойств). Если нужно сделать UNDO транзакция откатывается, если REDO накатывается.
Пример в коде:
// Указатель на объект, с которым работает интерфейс
// Детипизирован (интерфейс это не та штука, которая должна
// ломаться/перекомпилироваться при исправлении логики)
IPropertyEnabled *pSomeObj = ...;
// Значения, которые будем устанавливать, берутся, скажем из полей на форме.
int x = ...;
double y = ...;
std::string s = ...;
auto pCommand =
// Создаем новую пустую транзакцию
MakeTransaction()
// Открываем трамплин к объекту, все дальнейшие изменения будут происходить в нем
// и мы не хотим писать указатель каждый раз
.MakeTrampoline( pSomeObj )
// Устанавливаем свойства, при каждом вызове происходит валидация конкретного свойства
// (например, проверяется достоверность условия y >= 0, это делается на уровне логики
// интерфейсу не положено знать, какие значения правильные в данном контексте)
.SetPropertyValueAndValidate( "Property_X" /* имя свойства */, x )
.SetPropertyValueAndValidate( "Property_Y" /* имя свойства */, y )
.SetPropertyValueAndValidate( "Property_Z" /* имя свойства */, z )
// закрываем трамплин к объекту
.Close()
// закрываем транзакцию, здесь происходит валидация всего объекта в целом
// (например, может проверяться, что x < y)
.Close();
В данном примере, имена свойств просто строки, часто их удобнее хранить в отдельных .h файлах как константы. Весь код, естественно, помещен в try блок, если происходит какая-то ошибка (неверное значение, неверный тип, неверное имя свойства и т.п.) выкидывается исключение содержащее подробную информацию о том что произошло. Если это ошибка на уровне кода (неверный тип) ее можно быстро исправить. При уничтожении незакрытой транзакции все изменения отменяются автоматически.
Полученная команда помещается в стек UNDO/REDO. Команда, предоставляет интерфейс для ее отката/наката.
2. Сериализация.
Если все ПОСТОЯННЫЕ данные хранятся в свойствах (это не означает, что логика не имеет к ним прямого доступа для чтение), то сериализация работает централизованно через свойства. В самих объектах ничего писать не надо. При этом лего добавляются новые виды хранилищ. Кеш и прочие временные данные в свойствах естественно не хранятся.
3. Четкое разделение интерфейса и логики.
Интерфейс работает только со свойствами. Интерфейс не имеет доступа к .h файлам логики в принципе, там и своей работы по рисованию «кнопочек» хватает. Это гарантирует работу таких вещей как UNDO из коробки. Вследствии детипизированности свойств, а также наличия трамплинов преобразования интерфейсу не нужно использовать точно такие же типы, как и логике, достаточно «похожих». Например, логика может использовать вектора с кастомными аллокаторами, а интерфейс с обычными.
4. Возможность построения сложных фабрик объектов.
Идея такая: создаем пустой объект, инициализируем его через свойства. Если что-то пошло не так мы об этом узнаем (есть валидация). Дальнейшие действия зависят от того, насколько объект нужен и т.п. Например, можно использовать в играх для добавления возможности конструирования кастомных танков игроками. На танк можно наложить общие ограничения (типа мощность выстрела не более ЭН), которые могут быть весьма сложными. Интерфейс в этом участвовать не будет.
5. За счет отсутствия жесткой связи между свойствами и их реализацией, очень легко пишутся врапперы, которые предоставляют доступ к свойствам из других языков. Например, из скриптового языка типа VBA/LUA.
6. Также легко пишутся Property Browser-ы. При удачном выборе имен свойств становится возможным делать вещи вроде «для всех объектов масштаб увеличить на 10%» (пример не очень удачен (это легко делается и через виртульные функции), но какой есть)
ну и т.п.
А ещё очень интересны механизмы сквозного «склеивания» свойств в БД, слое бизнес-логики и представления. Когда одна и та же вещь и в БД, и в контроллере и в GUI называется одинаково. А то в enterprise-приложениях, чтобы добавить или изменить единственный «атрибут», приходится изменять массу кода и «пробрасывать» его через все слои абстракции. Имели опыт реализации подобной «серебрянной пули»?
Код в студию. Я уже предвкушаю продление жизни минимум на 10 лет.
Искренне не понимаю, зачем городить весь этот шаблонный хаос ради фич, для которых язык изначально не заточен? Есть же современные языки, гораздо более мощные, чем C++. Разговоры про суперпроизводительность — bullshit, т.к. большинство программистов не умеют писать эффективно ни на одном языке, а опытные и на Python напишут своё приложение, причём гораздо быстрее C++ников. И приложение будет работать с достаточной скоростью. Максимальная производительность большинству приложений не нужна. Нужно выжать последние мипсы-флопсы из проца — пишем критичный код на C.
Расширяемость языка тоже на очень низком уровне. Для элементарных задач метапрограммирования, которые у меня возникают очень часто, мощности C++ явно не хватает: примитивные текстовые макросы слишком слабы, а рекурсивные шаблоны смотрятся как спойлеры на «жигулях», или даже как пятая нога, торчащая из задницы.
Я из высокоуровневых языков использую D, Common Lisp и Python, а на C++ уже давно почти ничего не пишу, разве только для микроконтроллеров — там его использование ещё как-то оправдано. И вам советую перейти на более продвинутые языки, раз уж вам хочется, чтобы всё было, как у людей.
Насчет того, что удобнее просто взять другой язык согласен, но не всегда получается (кода много и там мат. моделирование, будет тормозить). К тому же property это как раз та штука, которая позволяет связать плюсы с другим языком.
А описанные вами «правильно» и «должны», как мне кажется, относится к небольшому классу задач, к тому-же обычно реализуемому на языках, отличных от С++.
В игровых движках польза от свойств сомнительна (хотя если в игре можно «грабить корованы», то для контроля этого процесса их можно применить)
Проперти - это просто такой вид функции (только без скобочек). Все остальное - Ваши выдумки
В любом случае, упражняться, это конечно хорошо, но упражняться в том, что по большому счету, нигде не нужно — я думаю, это как минимум расточительно.
class TestClass
{
public:
...
void _setStr(std::string s)
{
propStr = s;
}
Property<std::string, TestClass, WriteOnly> testStr;
private:
...
std::string propStr;
};
int main()
{
TestClass t;
{
TestClass *t1 = new TestClass;
t1->testStr = "1";
t = *t1;
delete t1;
}
t.testStr = "12";
...
return 0;
}
И все падант. Почему? В пропертях нет конструктора копирования, а есть поинтер на владельца.
Не зря на собеседованиях по C++ много гоняют на конструкторы \ деструкторы. В определенный момент подобные лаги выхватываешь взглядом на первом проходе глазками по коду :)
Так же хотелось бы как-то избегать падений если я забыл вызвать Init для проперти, ну человеческое исключение хотяб кидать, а не просто вылетать по битому указателю.
Сеттеры хотелось бы писать в стиле
void _setStr(const std::string &s)
{
propStr = s;
}
т.е. передавать значения по ссылке, не все пропертя могут быть легкими интегральными типами, мб. и тяжелые объекты
исправь:)
Объектно-ориентированные и структурные системы склонны подходить к проблемам с диаметрально противоположных направлений. Возьмите в качестве примера скромную запись employee. В структурных системах вы бы использовали тип struct и имели бы доступ к полям этого типа повсюду
из своей программы. Например, код для печати записи мог бы свободно повторяться в нескольких
сотнях мест программы. Если вы меняете что-то в основе, вроде изменения типа поля name с массива
char на 16-битные символы Unicode, то вы должны разыскать каждую ссылку на name и
модифицировать ее для работы с новым типом.
В хорошо спроектированной объектно-ориентированной системе было бы невозможно получить
доступ к полю name.
Позвольте мне повторить это, потому что эта концепция так фундаментальна:
невозможно получить доступ к полю внутри объекта, даже такому простому, как name в объекте
employee. Скорее всего вы попросите employee проявить какую-нибудь способность, такую как
«напечатать себя», «сохранить себя в базе данных» или «модифицировать себя, взаимодействуя с
пользователем». В этом последнем случае обработчик сообщений вывел бы диалоговое окно, которое
бы использовалось пользователем для ввода или изменения данных.
Ален Голуб, «Правила программирования С++».
Рекомендую очень внимательно отнестись к данному подходу.
Его «подсказывает» сам синтаксис: либо мы заводим простейшие структуры с открытыми для доступа членами (для семантической группировки данных в структуру), либо используем классы с закрытыми по умолчанию членами — как самостоятельно действующие единицы программы. Несмотря на одинаковую реализацию, это совершенно разные сущности.
Класс хранит всё внутри и работает как чёрный ящик, выполняя высокоуровневые запросы, обращённые к нему (открытые функции-члены). В соответствии с правилами инкапсуляции (которые, поверьте, имеют смысл), всё остальное закрыто для внешнего мира.
В этом смысле, стоит задуматься, что представляет собой реализуемое свойство (property) класса. Это действительно необходимость, то есть некое сообщение, запрос действия от класса? Или это просто усложнённый и завуалированный вариант открытого члена класса?
все смещения можно посчитать и так:
#include <iostream>
using namespace std;
#define this_base(type,member) ((type *)(this - offsetof(type,member)))
class C{
struct prop_x{
operator int()const {
return this_base(C,x)->x_getter();
}
prop_x & operator=(int xx){
this_base(C,x)->x_setter(xx); return *this;
}
};
int _x;
int x_getter()const{
cout<<"getter "<<_x<<endl; return 0;
}
void x_setter(int xx){
cout<<"setter "<<xx<<endl; _x = xx;
}
public:
prop_x x;
};
int main(){
C c;
c.x = 5;
int q = c.x;
}
но лучше в структуру свойства положить какие-то данные, а то 4 байта зря пропадет.
Но все же интересно, почему offsetof требует standard-layout type, и ругается (варнингом) на non-standard-layout type «C»?
int x_getter()const{
cout<<"getter "<<_x<<endl; return _x;
}
Вот кстати аналогичный вариант с offsetof http://xinutec.org/~pippijn/home/programming/cpp/properties
Ругается потому что у вас есть private. http://en.cppreference.com/w/cpp/types/is_standard_layout
К сожалению почти каждый класс в реальном проекте "non-standard-layout".
Property<int, TestClass, ReadWrite> testRW;
...
testRW.init(this, &TestClass::_getterRW, &TestClass::_setterRW);
как-то вот так, ближе к стилю BCB
typedef Property<int, TestClass, ReadWrite> PropInt;
...
PropInt testRW = { this, &_getterRW, &setterRW };
...
(CodeBlock / MinGW-gcc)
Property в C++