| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C/C++: Общие вопросы > Неожиданные особенности инкапсуляции |
| Автор: mr_kaspersky 16.11.2007, 20:37 |
| Добрый день, уважаемые жители форума ! Недавно я столкнулся с новой для меня проблемой в разработке на C++, хотя занимаюсь этим уже около двенадцати лет. За все это время я участвовал в нескольких крупных проектах с объектно-ориентированным уклоном и, надо сказать, непреодолимых трудностей не возникало. До поры, до времени. Собственно проблема такова: Я создаю некоторую сущность, наделяю ее некоторыми свойствами: /** An example */ struct S1 { /** Default constructor */ S1() { m_count = 0;} /** Increase internal variable */ void inc() {m_count++;} int m_count; /**< Counter */ }; Затем, где S1 видна, я начинаю с ней общаться: ... S1 s1; s1.inc(); std::cout << s1.m_count << std::endl; ... Недавно я обратил внимание, что (например в устной речи) часто используется слово "класс", и, как я понял по контексту, это ничто иное как struct. Недавно я с удивлением обнаружил зарезервированное слово class в синтаксисе языка. Да, вроде бы все хорошо, однако когда я меняю в определении S1 слово struct на слово class, я получаю ошибку компиляции ! Почему ? Пробовал я на разных компиляторах, gcc, intel, borland, нигде не выходит. Выдает сообщение об ошибке. Например, gcc говорит: ./example.cpp: In function 'int main()': ./example.cpp:7: error: 'S1::S1()' is private ./example.cpp:17: error: within this context ./example.cpp:10: error: 'void S1::inc()' is private ./example.cpp:18: error: within this context ./example.cpp:12: error: 'int S1::m_count' is private ./example.cpp:19: error: within this context Причем здесь private ? Частный, личный или, наконец, персональный. Ну да, он действительно принадлежит S1. В чем тут ошибка ? С нетерпением жду ваших советов. |
| Автор: Greeen 16.11.2007, 20:44 |
| Извините конечно, но вы уверены, что занимались языком C++? В C++ по умолчанию члены класса имеют доступ private. Добавлено через 1 минуту и 39 секунд В вашем примере конструктор класса, метод inc() и переменная m_count являются закрытыми. О чем вам компилятор, кстати, очень доходчиво говорит. |
| Автор: Daevaorn 16.11.2007, 20:55 |
| Greeen абсолютно прав, а автору топика совсем не вериться по части опыта. |
| Автор: bsa 16.11.2007, 23:21 | ||||
|
| Автор: archimed7592 17.11.2007, 17:04 |
За двенадцать то лет, можно было бы выработать рефлекс писать ++m_count |
| Автор: fish9370 17.11.2007, 22:27 | ||
а какая в данном случае разница? |
| Автор: Lazin 17.11.2007, 22:28 |
| ребят, надо быть проще, над вами тут кажется немного постебались, а вы повелись)) Добавлено через 1 минуту и 29 секунд для типа инт никакой, а для итераторов есть |
| Автор: Daevaorn 17.11.2007, 22:30 |
никакой. но говорит о стиле. |
| Автор: fish9370 17.11.2007, 22:37 | ||
ну и чем плох такой стиль, если понимать, что происходит.. как в данном случае, нарасчивать обычный счетчик? |
| Автор: Alek86 17.11.2007, 22:55 |
написано ж: ++m_count представь, что потом когда-то поменяется тут тип инт на твой итератор. пусть лучше уж сразу будет "правильный" код, чтоб менять как можно меньше |
| Автор: fish9370 18.11.2007, 01:16 | ||
там запятая была пропущена, см. поправку.. я все-таки не могу себе представить, чтобы изначально задуманный счетчик, вдруг, с фонаря, кто-то решил делать итератором.. 2 MAKCim: можно объяснить мне, в чем выражается эффективность? (при условии, что нам нужно нарасчивать счетчик) |
| Автор: DjoNIK 18.11.2007, 01:33 | ||
итератором можно использовать как "счетчик адреса" (если чуж очень грубо говоря) Как минимум в том, что при префиксной инкриментации не создается никаких временных объектов, а при постфиксной - создается |
| Автор: fish9370 18.11.2007, 01:44 | ||||
нет про счетчик адреса - это высосанно из пальца.. тут ясно идет речь о обычном счетчике, который обычно именуют count (или counter).. счетчик, например, в котором учитывается количество посетителей на сайте или число кликов.. или число проданых товаров..
а можно мне это продемонстрировать, как это выглядит на АСМе? |
| Автор: Daevaorn 18.11.2007, 01:58 |
Тут не нужен асм, а важна семантика С++ в отношении пре/пост-фиксного инкремента. В стандарте явно прописано, что и когда произходит. Понятно, что компилятор умный и всё оптимизирует и не будет создавать временный объект. Но зачем до этого доводить? Тем более, как уже сказали выше, в будущем меньше придется изменять при использовании другого более "тяжелого" типа. |
| Автор: fish9370 18.11.2007, 02:23 | ||
в будущем ничего менять не придется, еще раз повторяю, речь идет о обычном счетчике.. который задумывался как счетчик и используется как счетчик.. можно мне цитату из стандарта, а то что-то я подзабыл, что там конкретно написанно? |
| Автор: JackYF 18.11.2007, 03:16 |
Программа пишется не один раз. А хорошая программа - далеко не один раз. |
| Автор: fish9370 18.11.2007, 04:09 | ||
ну если это так, то никого не смущает, тот факт, что придется менять переменную m_count на нечто вроде iterator? всех только волнует, что нужно менять m_count++ на ++m_count? и то спорно, примеров пока так никто и не привел.. кто-то "блеснул стилем" и понеслась это круто, а это не круто.. только доказательства какие-то неубедительные.. по мне так это одно и тоже.. работы столько же.. планировать нужно заранее, меньше переделывать придется.. Добавлено @ 04:14 и еще мне нравится забота о компиляторе, несчастненьком, давайте будем все делать за него.. ему молоко за вредность давать нужно.. если код выглядит логичнее, то так оно и лучше.. а на проблемы компилятора мне начхать!! |
| Автор: Dims 18.11.2007, 07:05 | ||||
Это не совсем верно. В структуре все члены по умолчанию публичные. Чтобы их сделать приватными, нужно это явно указать. А в классе, наоборот, все члены по умолчанию приватные. Чтобы они были публичными, нужно это специально указать. А больше, действительно, отличий нет. В Вашем случае, чтобы работало, надо написать:
Здесь метка public: делает всё нижеследующего публичным, как в структуре. Собственно, без public/private инкапсуляция, как идея, становится неполной. По правилам инкапсулирования, Вы должны пометить private всё то, чем "не положено" пользоваться извне. В результате класс станет "чёрным ящиком", пользоваться которым можно только через небольшое число public членов. Залезть в "чёрный ящик" в обход предусмотренного нельзя, это его private зона. Добавлено @ 07:10 Мне тоже кажется, что никакой. Но я сам помню, действительно, что начиная с какого-то времени мне стало нравиться использовать префиксную форму. Правда, только в цикле for, а в данном случае я бы тоже написал постфиксно. Добавлено @ 07:18 Наоборот! В 12 как раз верится. Вот в 5 я бы не поверил. А 12 лет назад у нас не было даже компьютерных факультетов, а, может, и компьютеров (я уже забыл). Все учились самоучками, а самоучка отличается тем, что в его навыках всегда присутствуют вот такие вот "странности". Вполне можно поверить, что он 12 лет успешно использовал struct в крупных проектах. |
| Автор: Ln78 18.11.2007, 07:50 |
| Dims (и не только), неужели Вы всерьёз полагаете, что человек с таким ником, знающий слово инкапсуляция, не в состоянии разобраться в вопросе, который он задал? Не стоит воспринимать всё так уж буквально. |
| Автор: archimed7592 18.11.2007, 09:36 | ||
| Нафлудили то сколько... 0_о fish9370, тут дело больше не в "понимаю что происходит"/"не понимаю"/"заменю на итератор"/"не заменю", а в рефлексе. Если ты всегда используешь пре*кремент, и только когда нужно(реально нужно), пост*кремент, то никогда у тебя никаких проблем не возникнет Это тоже самое, что рефлекс писать const, писать explicit у конструкторов и подобные мелочи от которых "разницы" нет. Добавлено через 3 минуты и 43 секунды
Ага, и жил в заповеднике, где наглые обезьяны и носороги скрывали от него существование class |
| Автор: MAKCim 18.11.2007, 11:10 | ||||||||||
по семантике так естественно, на современных компиляторах в случае обычного инкремента обе формы дадут одинаковый результат но, как было уже сказано, тут дело в стиле и профессиональности кода Добавлено @ 11:17
для меня лично человек будет выглядеть более знающим (по крайней мере при первом знакомстве с его кодом), если он напишет ++count, а не count++ в случае если оба варианта в данном контексте равносильны кроме того, вопрос спорный, что выглядит более логичным по мне так эти два варианта оба логичны я еще раз повторяю, тут вопрос в стиле как говорится, покажи мне свой код, и я скажу кто ты |
| Автор: archimed7592 18.11.2007, 13:42 |
| Насчёт заботы о компиляторе: c-cast vs c++-cast void * vs T * assignment vs constructor уже упомянутое T vs const T int vs enumeration manual resource managment vs RAII exception unsafety vs exception safety Список можно долго продолжать. Несмотря на то, что, по сути, для "X vs Y" результат будет одинаковым как при использовании X, так и Y, то что во втором "столбце" должно быть как минимум привычкой, а по хорошему - рефлексами, а при использовании конструкций из первого "столбца" внутри должен срабатывать громогласный alarm. |
| Автор: fish9370 18.11.2007, 17:19 |
| приму к сведению.. |
| Автор: JackYF 18.11.2007, 17:47 | ||
ППКС. Не всегда удаётся полностью это сделать, но всегда, имхо, надо стараться и уж никак не ратовать за "компилятор умный, он сам всё за меня сделает". Это не оправдание плохого стиля. |
| Автор: archimed7592 18.11.2007, 17:59 |
0_o. Я понимаю, что не всегда удаётся выдерживать стиль полностью, но, тот короткий список, что я привёл - IMHO, может "не всегда удаваться" только в случае жесткого(очень жесткого) навязывания этого какими-либо внешними факторами, на которые ты никак не можешь повлиять(габаритная, криво спроектированная библиотека, написание wrapper'а к которой - большая потеря времени; начальство, навязывающее некоторый стиль, не соответствующий твоему идеалу и т.п.), но, даже в таких случаях такие места очень просто локализовать, т.о. исключив распространения плохого стиля повсеместно в коде... |
| Автор: UnrealMan 19.11.2007, 02:21 | ||
Только не в отношении А это вообще непонятно что такое. |
| Автор: archimed7592 19.11.2007, 04:32 | ||||
Почему? Где хороши c-cast'ы? Чем хорошо не делать переменную константной, если ей незачем меняться?
vs
Добавлено через 37 секунд Пример с цветами конечно неудачный(цвета как раз лучше представлять в виде машинного слова), но, если заменить на кошек, собак и утюги, то будет самое то |
| Автор: Dims 19.11.2007, 12:25 | ||
Конечно, вероятность шутки велика. Но точно мы знать этого не можем. Кроме того, если это шутка, то тоже странная. Не очень смешная. |
| Автор: UnrealMan 19.11.2007, 14:53 | ||
Там, где смысл преобразования очевиден и один вид преобразования никак не может быть спутан с другим. Например, если нужно произвести дробное деление, имея две целочисленные величины: int x, y; .... double result = (double)x/y; static_cast здесь не имеет никаких преимуществ. Наоборот, поскольку он занимает гораздо больше места, его присутствие только ухудшит читаемость кода. Приведение в функциональной нотации (которое всё-таки относится к C++-cast) - это почти то же самое, что C-cast.
Дилемма в том, что не всегда точно известно, будет ли переменная меняться. Делая переменную неконстантной, мы оставляем пространство для манёвра - читай делаем код сопровождаемым. Кроме того, const не всегда способствует читаемости, поскольку опять же удлиняет и нагромождает код. Поэтому надобность по умолчанию ("на уровне рефлексов") всюду пичкать const как минимум спорна. "Незачем меняться" - это слабый аргумент для того, чтобы делать саму переменную константной (не хочешь менять - не меняй и всё). Вот если объект обязан быть неизменным в пределах достаточно большой области видимости, тогда другое дело. Неудачное - это название сравнения "int vs enumeration". |
| Автор: archimed7592 19.11.2007, 15:04 | ||
А что мешает убрать спецификатор const, в случае необходимости изменения переменной? И способствует самодокументированности кода - сразу понятно, что данная переменная далее по тексту нигде не изменится. Ну вот Кусто, по всей видимости, понял, что я имел ввиду |
| Автор: UnrealMan 19.11.2007, 15:24 | ||||
А не факт, что придётся убрать только этот спецификатор. Например, может понадобиться заменять все const_iterator на iterator, потому что нам захотелось модифицировать контейнер, который мы раньше считали неизменным, используя итератор.
А ты уверен, что это такая уж полезная информация? Особенно если "далее по тексту" - это одна-две-три строчки (дальше переменная просто не живёт) и её неизменность и без того очевидна. |
| Автор: Ln78 19.11.2007, 15:24 | ||
Думаю, если бы вопрос топикстартеру был интересен, вряд ли он ограничился бы единственным сообщением. Да и сам стиль вопроса меня наводит на мысль, что это была попытка породить дискуссию подобную http://www.gamedev.ru/code/forum/?id=19939 |
| Автор: JackYF 19.11.2007, 16:02 | ||||
увы, я столкнулся с тем, что если в наследие досталась пачка местами кривого кода, в котором "забыли" порасставлять const, причём даже в самых очевидных местах и я не могу передать в функцию const char*, так как она принимает char*, хотя на деле его не изменяет, а эта функция используется повсеместно в самых разных местах и даже программах, и сама завязана на ещё одной такой же... то тут возникает логичный вопрос: переписать и переотладить тысячи строк кода или старые куски использовать по-старому, а уже новый код писать нормально. Я же сказал - "не всегда", но надо стараться. Что я с большим рвением и делаю. Кстати, да. И понял и поддерживаю. Я тебя вообще хорошо понимаю, как-то так сложилось имхо, дискуссии без личных выпадов полезны, это же не флейм без темы.
Очень правильно. Тогда да, мы пойдём и заменим типы. А вот если наоборот, то есть если мы забудем поставить const там, где сейчас у нас константная работа и случайно запишем по итератору, то компилятор не даст по рукам и будет прав. Имхо, чем больше const там, где переменные константны, пускай даже сейчас, тем лучше. Чем строже ведёт себя программист по отношению к коду, тем прямее получается код. Да, довольно полезная. Через неделю в эту функцию вставят ещё пять строчек. А через месяц ещё пять. Много понадобится памяти, дабы запомнить, что где "должно" быть константным, но const не поставлено... |
| Автор: UnrealMan 19.11.2007, 16:34 | ||
Ну и что? Это достоинство const, да. Но помимо достоинств, есть и недостатки. Что весомее? Тут нет однозначного "лучше". И лично я предпочту воздержаться от использования const. Начали-то с оператора ++, там префиксный ++ однозначно как минимум не хуже, чем постфиксный. В случае с const такой однозначности нет. Ну, для тебя, может, полезная, для меня - абсолютно бесполезная и даже вредная. |
| Автор: JackYF 19.11.2007, 18:51 |
const на скорость не влияет, поэтому показатель субъективный. Следовательно, я допускаю, что кому-то const использовать не всегда удобно. Принято. Ясно. |
| Автор: zkv 19.11.2007, 19:22 |
теоретически он может помочь оптимизатору. |
| Автор: Alek86 19.11.2007, 19:28 |
не буду спорить, но товарищ Саттер насчет этого довольно резко высказывался (Герб Саттер "Новые сложные задачи на C++") |
| Автор: UnrealMan 19.11.2007, 20:42 |
А может и наоборот нагадить, если по совету Мейерса возвращать константный объект вместо обычного. В критичном к скорости участке кода без const можно вызвать неконстантный метод прямо для временного объекта, вместо того, чтобы тупо создавать копию. Например, присваивание result = a+b; без const элементарно заменяется swap-ом (a+b).swap(result); |
| Автор: bsa 19.11.2007, 21:14 | ||
операция swap в общем случае более затратна, чем присваивание. И к тому же, менее наглядна. Посмотрел мнение Саттера по поводу const. Думаю, он прав. const редко дает оптимизацию, зато позволяет избегать ошибок. А разговоры о том, что когда-то метод не менял свой параметр, а потом вдруг стал менять и в случае с использыванием const возникнет куча проблем - глупы. Если у тебя такое получилось, то сам виноват - неправильный дизайн. Лучше уж написать еще одну функцию, которая выполняет нужные действия, чем ловить баги от "неправомерного" использования старой (в надежде, что она не меняет параметр). И вообще, зачем писать функции на сотни строк, в которых может возникнуть проблема замены большого количества "const_iterator" на "iterator"? Имхо, 30-60 строк на функцию/метод - больше не стоит без крайней необходимости. |
| Автор: archimed7592 19.11.2007, 22:02 | ||||||||||||
Ну, тут уже другая дилема - всего не предусмотришь... Ессно, случаи разные бывают.
Не, не, не... Даже в 1-ой строке бывает минибаг, который сразу не увидишь и которого при строгой константности могло бы не быть. Ты же отлично знаешь сколько в С++ тонкостей Ещё один ньюанс: если ты НЕ видишь спецификатора const, то тебе нужно помимо общей логики работы последующих 3 строчек ещё и задумываться о возможности изменения данной переменной. Учитывая, опять же, невообразимое множество тонкостей языка - это, IMHO, совсем не нужная нагрузка на мозг.
Кстати, почему-то такие тупые "вопросы" почему-то частенько порождают такие интересные дискуссии
Вся наша жизнь состоит из альтернатив. Универсальных решений/подходов не бывает. Но при прочих равных, я бы предпочёл поставить спецификатор const. К примеру, раньше мне было непонятно какое может быть применение у нестатических константных членов. Сейчас мне их применение очень даже понятно, и я переодически их использую(в частности в текущем проекте их просто туева хуча).
Ну я бы не сказал что прям таки без крайней. У меня частенько возникает потребность писать "одноразовый" код, разбивать который на маленькие ф-ции большого смысла нет, а скорее даже может запутать. Да и таскать туда-сюда контекст в таких случаях бывает накладно. |
| Автор: bsa 19.11.2007, 23:48 | ||||
Именно это я и имел в виду под "крайней необходимостью". Действительно, никакого смысла городить огород ради программы на 200 строчек. |
| Автор: archimed7592 20.11.2007, 00:02 | ||
Ты не понял |
| Автор: UnrealMan 20.11.2007, 00:09 | ||||
Для потенциально больших объектов swap гораздо лучше. Надо ли говорить, что в большом количестве случаев присваивание по ряду причин выгодно реализовывать, используя идиому swap?
Читай внимательней топик. Речь шла о переменных.
Это ещё зачем? Ну так и не ищи себе вымышленных проблем. |
| Автор: bsa 20.11.2007, 12:20 | ||||||
Я все тогда понял. Просто ответил не очень корректно на два замечания. Не думаешь же ты, что я ради того, чтобы уложиться в 50 строчек буду "таскать контекст"? Ничего подобного. ладно, это уже офтопик.
|
| Автор: archimed7592 20.11.2007, 12:38 | ||
А как же специализированный swap? |
| Автор: bsa 20.11.2007, 12:56 | ||||
в общем случае специализированного же нет. но даже для int'а он работает медленней (даже с использованием инструкции ассемблера xchg) - два чтения и две записи в память. Да, конечно, в частном случае, например, для std::string, std::vector и т.п. он может работать быстрей. Но если я для своего собственного класса специализацию не написал или если он просто большой, то будет медленней. Разве не так? |
| Автор: UnrealMan 20.11.2007, 13:47 | ||
| Не понял, при чём тут STL-то? Я же метод swap вызвал. Для временного объекта по стандарту C++ ты даже не имеешь права непосредственно вызвать STL-ный swap. Повторюсь, это
каноническая форма записи оператора =, применяющаяся довольно часто. |
| Автор: bsa 20.11.2007, 14:03 |
| Конструктор копирования работает быстрей, чем мог бы работать оператор присваивания, копирующий все поля? |
| Автор: UnrealMan 20.11.2007, 14:14 | ||
Правда, на хорошо оптимизирующих компилях замена канонического присваивания swap-ом ничего не даст - копирующий конструктор по-любому не будет вызван
Нет. |
| Автор: mr_kaspersky 22.11.2007, 01:46 |
| Помечаю вопрос решенным, всем спасибо за внимание. |
| Автор: archimed7592 22.11.2007, 02:08 |
| Автор: MAKCim 22.11.2007, 10:28 |
| UnrealMan, bsa, чем спорить, взяли бы и посмотрели на сгенерированный компилятором код все станет сразу понятно, что быстрее, а что медленнее |