А>довольно продолжительное время ломаю голову — когда делать функцию константной, а когда в этом нет необходимости???
Однозначного "механического" ответа на этот вопрос не существует. Зачастую в качестве "механического" ответа на этот вопрос приводят что-то вроде "если функция не меняет содержимого класса, то ее следует сделать константной, в противном случае — неконстантной", где под словом "меняет" подразумевается фактическое изменение одного их внутренних полей класса. Во многих случаях это правило дает правильный (или почти правильный) ответ на поставленный вопрос. Но на самом деле в общем случае это правило является лишь простейшим первым приближением к правильному ответу — оно реализует идею физической константности. На самом делен язык С++ более-менее приспособлен к реализации идеи логической констаности. Делать ли метод класса константым или нет — вопрос, который должен решаться на уровне дизайна класса и проекта.
У твоего класса есть некоторое состояние, которое видно извне через интерфейс этого класса. Константными должны быть методы, которые не меняют этого состояния. Соответственно, с модификатором 'const' должны объявляться те объекты этого класса, которые не допускают изменения этого состояния. Состояние в данном случае совсем не эквивалентно банальному бинарному содержимому полей объекта. Поля могут изменяться — а состояние при этом оставаться тем же.
Классический пример — класс некого ассоциативного хранилища (массив, например), реализованного с использованием кэширования запросов. Каждый приходящий запрос сначала ищется в небольшом кэше недавних запросов, а затем, если поиск в кеше ничего не дал, уже собственно в хранилище. Результат поиска возвращается наружу, а также добавляется в кэш последних запросов. Если поиск в собственно хранилище — дорогая операция, а одинаковые запросы часто приходят подряд (или почти подряд), наличие совсем небольшого кэша может дать огромный выигрыш в производительности. Но с точки зрения интерфейса класса — это не более чем ассоцитивное хранилище. Наличие кэша внутри этого класса не является частью его интерфейсной спецификации. Снаружи о кэше никто не знает и не должен знать. Кэша может и не быть вообще — интерфейсная функциональность класса от этого не пострадает. (Может пострадать производительность, но не функциональность.) В такой ситуации состояние кэша не является частью интерфейсного состояния класса. Операция поиска данных по ключу в хранилище не меняет состояния класса и поэтому соответвующий метод класса следует объявить как 'const'. В то же время каждая операция поиска потенциально меняет содержимое кэша, хранящегося внутри этого класса. Т.е. данное использование модификатора 'const' противоречит идее физической констатности, но нисколько не противоречит идее логической константности. Проблемы с протворечием с идее физической константности (о которых нам, скорее всего, не забудет сообщить компилятор) легко устраняются при помощи объявления самого кэша с модификатором 'mutable'. Именно для этого модификатор 'mutable' и предназначался.
Предыдущий пример демонстрирует ситуацию, когда физическая констаность требует отсутствия модификатора 'const' в объявлении метода, в то время как логическая константность говорит, что модифкатор 'const' нужен. Возможна и обратная ситуация. Пусть некторый класс 'P' содержит в себе поле 'sptr' типа 'указатель на объект класса S'. Пусть также в классе 'P' есть некий метод 'P::ChangeS()', который модифицирует указываемый полем 'sptr' объект класса 'S', но ничего не меняет в объект класса 'P'. Должен ли этот метод быть константым? С точки зрения физической константности — да, должен (или, по крайней мере, может), ибо физически объект класса 'P' никак не меняется методом 'P::ChangeS()'. Константность объекта типа 'P' в С++ формально никак не распространяется на указуемый полем 'sptr' объект типа 'S', поэтому объявление 'P::ChangeS()' с модификатормо 'const' никак не помешает нам поменять последний. А вот на логическом уровне все не так просто. Все зависит от того, что за принцип дизайна реализуется наличием в классе 'P' этого указателя 'sptr'. Это агрегация? Или просто невинная ссылка? Если это агрегация и состяние указуемого объекта класса 'S' как-то влияет на состояние объекта класса 'P', то с точки зрения дизайна изменение состояния этого указуемого объекта означает изменение состояния и объекта класса 'P' тоже. В таком случае метод 'P::ChangeS()' не должен иметь модификатора 'const' в своем объявлении. Если же указатель 'sptr' реализует просто неагрегирующую ссылку от 'P' к 'S', то можно объявить 'P::ChangeS()' с модификатором 'const'.
Вот такие вот дела. Константность в дизайне программы — понятие логическое, а не физическое. Решения о том, какие операции являются константыми, а какие нет, принимаются уровне дизана (программы, модуля или отдельного класса), и на основе этих решений затем делается расстановка модификаторов 'const' в объявлениях методов, параметров, объектов и т.д. в процесе реализации.
Здравствуйте, WolfHound, Вы писали:
WH>Здравствуйте, Аноним, Вы писали:
А>довольно продолжительное время ломаю голову — когда делать функцию константной, а когда в этом нет необходимости???
А>объясните человеческим языком, пожалста. WH>Если она не должна менять обьект то она должна быть константной.
На самом-то деле вопрос гораздо сложнее, чем кажется. Бездумное использование этого модификатора (всовывание его всюду, где только можно), способно создать немало неприятностей.
Пример. Допустим, у нас есть некий класс A, от которого в принцие можно наследоваться (т.е. не final и не sealed), с методом doSomething. Реализация этого метода в этом же классе не меняет члены данных. Стоит ли объявить ее const? Предположим, мы так и сделали.
class A {
private:
int i;
public:
virtual void doSomething() const {
printf("%d\n", i);
}
};
Пока все замечательно. Далее пишется отпределенное количество кода, где ссылка на объект класса A в другие методы и функции, в которых нужно всего лишь вызвать метод doSomething, передается как константная. Компилятор честно за этим следит и ненавязчиво подсказывает если что. В общем, хорошо.
И вдруг, нам понадобилось создать класс B унаследованный от A и переопределить в нем doSomething. И тут оказывается, что B::doSomething меняет члены данных класса B, поэтому не может быть константным!
Можно воспользоваться последними достижениями прогрессивного человечества -- модификатором mutable и объявить с его помощью все члены-данные, которые меняются в методе B::doSomething (а заодно и члены-данные, которые меняются и в других "константных" методах). При этом наблюдается странная картина, мы сознательно избавляемся от тех благ, которые сулил const! Зачем тогда надо было его городить?
class B: public A {
private:
intmutable j;
public:
virtual void doSomething() const {
j += i; // меняется член-данное
printf("%d\n", i);
}
};
Можно пойти другим путем и изменить интерфейс базового класса (убрать const из объявления A::doSomething). А это, как правило, означает, что надо заставить компилироваться некое количество кода, которое было написано до этого.
Поэтому, я считаю, что если есть хоть какое-то сомнение в необходимости constа, что лучше его не использовать.
Исходя из своего опыта, могу сказать, что в большинстве случаев:
1. constникогда не следует использовать в объявлении методов интерфейсных классов. Модификатор const в данном случае навязывает разработчику классов (наследующихся от заданных интерфейсных) некое внутреннее представление и, как следствие, нарушает инкапсуляцию.
2. constследует использовать в объявлении копи-конструкторов, операторов присваивания, различного рода операторов сравнения и прочих операторов, в которых по смыслу необходима константность объекта. Так же во вспомагательных методах, которые должны вызываться их этих операторов и таких же вспомагательных методов, должны быть константными. Как правило, такие классы относительно простые, и имеют очевидное внутреннее представление, которое не будет радикально меняться.
3. constследует использовать при передаче строк в качестве параметра (char const * или const char *)
4. constжелательно использовать при объявлении членов-данных, которые не должны меняться на протяжении всей жизни объекта.
[skip]
Д>На самом-то деле вопрос гораздо сложнее, чем кажется. Бездумное использование этого модификатора (всовывание его всюду, где только можно), способно создать немало неприятностей.
Бездумное программирование вообще создаёт много неприятностей
Д>Пример. Допустим, у нас есть некий класс A, от которого в принцие можно наследоваться (т.е. не final и не sealed), с методом doSomething. Реализация этого метода в этом же классе не меняет члены данных. Стоит ли объявить ее const? Предположим, мы так и сделали. Д>
Напротив, очень даже странно, функция doSomething() ни входных, ни выходных параметров не имеет, возвращаемого значения тоже нет и объект не меняет. Что же она должна делать? (Только писать в std::cout "Hello world!"?! )
Короче "const" тут сразу под большим вопросом.
Д>Далее пишется отпределенное количество кода, ... В общем, хорошо.
Д>И вдруг, нам понадобилось создать класс B унаследованный от A и переопределить в нем doSomething. И тут оказывается, что B::doSomething меняет члены данных класса B, поэтому не может быть константным!
А мне кажется странным, что у нескольких классов до этого функция doSomething была константной, а у нового класса эта функция оказывается неконстантной. Может быть новый класс делает в doSomething чего не должен делать? А может...
Д>Поэтому, я считаю, что если есть хоть какое-то сомнение в необходимости constа, что лучше его не использовать.
Не формализируемо, а посему неприменимо
Д>Исходя из своего опыта, могу сказать, что в большинстве случаев: <...>
P.S. Есть книги, которые посвящены подобным вопросам. ("Что и когда стоит или не стоит использовать в C++") Может стоит заглянуть в раздел сайта посвященным книгам по C++?
Здравствуйте, Дмитро, Вы писали:
Д>Разве во второй реализации члены-данные m_value1, m_value2 и прочее не относятся к состоянию объекта? Что тогда к состоянию объекта этого класса относится?
Состояние объекта — это то, что ожидает получить клиент класса.
void f(Settings *settings) {
int v1 = settings->GetValue1();
int v2 = settings->GetValue1();
// v1 == v2!!!
}
Константностью метода GetValue1() автор класса Settings подчеркивает, что если не вызываются неконстантные методы класса, то последовательные вызовы GetValue1() будут давать одно и то же значение, которое с точки зрения клиента является частью состояния класса.
Здравствуйте, Дмитро, Вы писали:
Д>Исходя из своего опыта, могу сказать, что в большинстве случаев:
Д>1. constникогда не следует использовать в объявлении методов интерфейсных классов. А>Не согласен у методов аля "Get", модификатор const должен быть в подавляющем большинстве случаев.
Д>Методы аля "Get" могут возвращать данные, которые могут и не храниться непостредственно в объекте. Они могут читать из файла, делать запросы по сети, устанавливать и разрывать при этом соединения, кешировать эти данные наконец. При этом, возможно, что-то в классе и поменяется. Но пользователю класса нужно только получить эти данные. И его не должно беспокоить каким образом они были получены. Его не должно беспокоить, поменялся ли какой-нибудь внутренний указатель или дескриптор сокета, к которому он все равно доступа, может быть, никогда и не получит. Как получить эти данные -- забота разработчика класса, и я считаю, что он, в свою очередь, не должен заботиться о том, как избавиться от нежелательной константности. Кстати, ты не рискнул сказать "во всех случаях", ты сказал мягче "в подавляющем большинстве случаев". Полагаю, ты сталкивался с теми случаями, когда все же не нужно было делать метод константным.
вот всякие кеширования и прочие временные и вспомогательные радости стоит объявлять mutable, ибо они не определяют состояние объекта, и, соответственно, не влияют на его константность. А дескриптор сокета, открытого временно и по случаю, не определяет состояние объекта, посему должен быть mutable.
Д>Модификатор const в данном случае навязывает разработчику классов (наследующихся от заданных интерфейсных) некое внутреннее представление и, как следствие, нарушает инкапсуляцию. А>Не совсем понял. А>Во-первых сам интерфейс есть некое навязывание. (Не хочешь навязывания, есть замечательный () void *, а также возможность сериализовать объект в поток байт ) А>Во-вторых каким это образом const разрушает инкапсуляцию!?
Д>Интерфейс не навязывает внутреннее представление. Внутренее представление может варьироваться не меняя при этом интерфейса. Собственно, в этом и назначение интерфейса -- оградить пользователя класса от его (класса) внутренностей. Д>Однако, если я объявляю метод константными, то я подразумеваю, что состояние объекта не будет меняться. Т.е. из всех возможных реализаций заданного класса я отказываюсь от тех, которые меняют состояние (конечно, могу и не отказаться воспользовавшись приемами устранения нежелательной константности). Именно это я подразумевал, когда говорил, что const нарушает инкапсуляцию (кстати, именно нарушает, а не разрушает). Модификатор const навязывает какую-то определенную реализацию. Причем, привязка к реализации делается слишком рано -- на этапе проектирования интерфейсов.
состояние объекта — это тоже деталь его интерфейса, хоть и не оформленная в виде метода.
И если какой-то метод не должен менять состояние объекта, то это должно быть прописано в прототипе этого метода.
А то, как это все устроено внутри объекта, не имеет никакого отношения к его интерфейсу вообще, и к константности методов, в частности.
Здравствуйте, jazzer, Вы писали:
J>вот всякие кеширования и прочие временные и вспомогательные радости стоит объявлять mutable, ибо они не определяют состояние объекта, и, соответственно, не влияют на его константность. А дескриптор сокета, открытого временно и по случаю, не определяет состояние объекта, посему должен быть mutable.
Пример. Допустим, мы разрабатываем класс, в котором хранятся параметры чего-либо. Каждый параметр имеет свой геттер (например, значение параметра value1 можно получить вызвав getValue1). Параметры могут, например, загружаться их файла.
Вот прямолинейная реализация. Параметры загружаются сразу в момент создания объекта.
class Settings {
private:
int m_value1;
int m_value2;
int m_value3;
int m_value4;
// . . .void loadFromFile();
public:
Settings() {
loadFromFile();
}
int getValue1() const {
return m_value1;
}
// . . .
};
Вот реализация с отложенной загрузкой. Параметры загружаются только тогда, когда в них возникает необходимость.
class Settings {
private:
bool mutable m_bLoaded;
int mutable m_value1;
int mutable m_value2;
int mutable m_value3;
int mutable m_value4;
// . . .
// здесь const фиктивный. он нужен для того,
// чтобы мог вызываться из константных методовvoid loadFromFile() const;
public:
Settings(): m_bLoaded(false) {
}
int getValue1() const {
if(!m_bLoaded)
loadFromFile();
return m_value1;
}
// . . .
};
Члены-данные m_value1, m_value2 и прочее были объявлены mutable для того, чтобы в них можно было загрузить значения внутри геттера.
Разве во второй реализации члены-данные m_value1, m_value2 и прочее не относятся к состоянию объекта? Что тогда к состоянию объекта этого класса относится?
J>состояние объекта — это тоже деталь его интерфейса, хоть и не оформленная в виде метода. J>И если какой-то метод не должен менять состояние объекта, то это должно быть прописано в прототипе этого метода. J>А то, как это все устроено внутри объекта, не имеет никакого отношения к его интерфейсу вообще, и к константности методов, в частности.
Модификтор const, на мой взгляд, играет две роли. Первая, он выполняет роль некого дискриминанта в интерфейсе класса. Он как бы в общем интерфейсе выделяет подинтерфейс, состоящий только их константных методов, которые не меняют состояние объекта. Вторая, он является указанием компилятору, чтобы тот предотвращал изменения членов-данных внутри константных методов. Как правило, изменение членов-данных приводит к изменению состояния объекта. В этом случае, эти две роли, являются взаимодополняющими. Однако члены-данные -- это не состояние. Они определяют его, но не являются им. Для класса "рациональных чисел" "две третьих" и "четыре шестых" -- может быть одним и тем же состоянием, пользователь класса может не иметь возможность в принципе их различать. Так же как и для "класса плоских углов меньших 360 градусов" 50 градусов и 410 -- это одно и тоже, хотя представлены они по разному. В недрах класса одно представление может переходить в другое (рациональное число может сокращаться, а угол приводиться к определенным пределам) не меняя при этом состояние объекта. А компилятор об этом не знает, в потому и ругается. А вместе с ним, ругается и разработчик класса, который ставит mutable или прибегает к иным приемам только для того, чтобы успокоить компилятор.
Обсуждение этой темы как мне кажется, склонно перерасти в религиозную войну. Наверное, не может быть однозначного ответа, когда нужно ставить const, а когда нет. Решение, которое для одного является образцом совершенства, для другого может показаться уродливым и некрасивым. И наоборот. Я не думаю, что что-то сможет меня быстро переубедить. Равно как, далек от мысли, что я кого-то переубедил. Но в любом случае, готов выслушать любые мнения.
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, все кто ответил. А>я понял так, поправте если не так, что определять когда следует использовать const исходя в первую очередь из обеспечения безопасности для объекта. А>безопасность объекта — приоритет номер 1. А>.................... — приоритет номер 2. А>спасибо, Дмитро, приятно было услышать конструктивное прояснения ситуации, обязательно возьму на вооружение Ваш опыт.
А>спасибо всем.
Нет, не безопасности объекта, а ясного выражения того, что представляют из себя класс и его методы.
Безопасность лишь вытекает отсюда.
например, в любом классе функция dump(), распечатывающая состояние объекта, просто обязана быть константной, потому что простой дамп не может изменить состояние объекта. А если ты для обеспечения быстрого дампа завел строчку, в которой этот дамп лежит, и при каждом вызове dump() проверяешь, изменился ли объект (у тебя есть для этого соответствующий флаг, например), и, если он не изменился, просто возвращаешь эту строчку, а если изменился, создаешь новую, ее возвращаешь и флаг валидности внутренней строчки с дампом сбрасываешь в false, то у тебя появляются по крайней мере 2 сущности, которые будут в этом методе изменяться, тем не менее, они не определяют состояние объекта, интересное пользователям этого класса (т.е. доступное через соответствующие публичные члены: они должны возвращать одно и тоже и до дампа, и после) — ты должен сделать эти 2 сущности mutable, но оставить функцию константной.
Так что, определяя, должна ли быть функция константной, смотри на это с точки зрения константности обекта в терминах доступных возвращаемых публичным интерфейсом значений (да и просто по сути, по логике и исходя из здравого смысла ^) )
Здравствуйте, Аноним, Вы писали:
А>Бездумное программирование вообще создаёт много неприятностей
Не стоит обобщать мои слова доводя их до банальности. Далее о тексту я привожу пример и разъясняю свои точку зрения.
А>Напротив, очень даже странно, функция doSomething() ни входных, ни выходных параметров не имеет, возвращаемого значения тоже нет и объект не меняет. Что же она должна делать? (Только писать в std::cout "Hello world!"?! ) А>Короче "const" тут сразу под большим вопросом.
Это абстрактный пример. Метод A::doSomething не меняет значения членов-данных класса A -- вот что важно . Как он это делает (т.е. не меняет члены-данные), печатает ли "Hello world" или еще что-то, не имеет принципиального значения. Наверное, чтобы пример был более убедительным, он должен был бы, как минимум, что-то возвращать. С этим я согласен.
То, что я хотел сказать, так это то, что бывают такие ситуации, когда методы в классе (независимо от количества и типов параметров и типа возвращаемого значения) не меняют члены-данные и при этом выполняют полезную работу.
Д>Далее пишется отпределенное количество кода, ... В общем, хорошо.
Д>И вдруг, нам понадобилось создать класс B унаследованный от A и переопределить в нем doSomething. И тут оказывается, что B::doSomething меняет члены данных класса B, поэтому не может быть константным!
А>А мне кажется странным, что у нескольких классов до этого функция doSomething была константной, а у нового класса эта функция оказывается неконстантной. Может быть новый класс делает в doSomething чего не должен делать? А может...
Нет, не кажется. Если функция стала константной просто потому, что она не меняла члены-данные своего класса, то такое запросто может быть. Я сам против такого принципа определения того, должна ли функция быть константной, и весь свой предыдущий пост я критиковал именно этот принцип.
Д>Можно воспользоваться последними достижениями прогрессивного человечества -- модификатором mutable и объявить с его помощью все члены-данные, которые меняются в методе B::doSomething... А>Логичнее было бы воспользоваться другим достиженим прогрессивного человечества — const_cast.
Признаюсь, забыл про const_cast. А вспомнил, почему-то, только про mutable. Способов избавиться от нежелательной константности можно придумать много, но сам факт появления этой нежелательной константности говорит о недальновидности проявленной на этапе проектирования интерфейсов классов.
А>А еще логичнее перепроектировать новый класс или подвергнуть рефакторингу всю иерархию.
Этот вариант я рассматривал. Лично я склоняюсь более к нему. Однако он имеет свои недостатки -- нужно что-то переделывать, переписывать, исправлять. Можно было этого как-то избежать? Да, не сделали бы метод константным -- не надо было бы ничего переписывать.
Д>Исходя из своего опыта, могу сказать, что в большинстве случаев:
Д>1. constникогда не следует использовать в объявлении методов интерфейсных классов. А>Не согласен у методов аля "Get", модификатор const должен быть в подавляющем большинстве случаев.
Методы аля "Get" могут возвращать данные, которые могут и не храниться непостредственно в объекте. Они могут читать из файла, делать запросы по сети, устанавливать и разрывать при этом соединения, кешировать эти данные наконец. При этом, возможно, что-то в классе и поменяется. Но пользователю класса нужно только получить эти данные. И его не должно беспокоить каким образом они были получены. Его не должно беспокоить, поменялся ли какой-нибудь внутренний указатель или дескриптор сокета, к которому он все равно доступа, может быть, никогда и не получит. Как получить эти данные -- забота разработчика класса, и я считаю, что он, в свою очередь, не должен заботиться о том, как избавиться от нежелательной константности. Кстати, ты не рискнул сказать "во всех случаях", ты сказал мягче "в подавляющем большинстве случаев". Полагаю, ты сталкивался с теми случаями, когда все же не нужно было делать метод константным.
Д>Модификатор const в данном случае навязывает разработчику классов (наследующихся от заданных интерфейсных) некое внутреннее представление и, как следствие, нарушает инкапсуляцию. А>Не совсем понял. А>Во-первых сам интерфейс есть некое навязывание. (Не хочешь навязывания, есть замечательный () void *, а также возможность сериализовать объект в поток байт ) А>Во-вторых каким это образом const разрушает инкапсуляцию!?
Интерфейс не навязывает внутреннее представление. Внутренее представление может варьироваться не меняя при этом интерфейса. Собственно, в этом и назначение интерфейса -- оградить пользователя класса от его (класса) внутренностей.
Однако, если я объявляю метод константными, то я подразумеваю, что состояние объекта не будет меняться. Т.е. из всех возможных реализаций заданного класса я отказываюсь от тех, которые меняют состояние (конечно, могу и не отказаться воспользовавшись приемами устранения нежелательной константности). Именно это я подразумевал, когда говорил, что const нарушает инкапсуляцию (кстати, именно нарушает, а не разрушает). Модификатор const навязывает какую-то определенную реализацию. Причем, привязка к реализации делается слишком рано -- на этапе проектирования интерфейсов.
Те четыре пункта, которые я приводил в своем посте никак не претендуют на полноту, не претендуют на то, чтобы быть базисом (чтобы одно не являлось частным случаем другого). Это одни из тех эмпирических правил, которыми я пользуюсь бессознательно. Я их придумал и сформулировал пока писал пост. Причем первый придумал сразу, а остальные додумывал как противовес первому. Эти четыре пункта не котегоричны. Их не стоит передергивать и поправлять "нет такого правила!" или "это частный случай!". Конечно, я ведь не ставил себе целью составить правила на все случаи жизни. В любом случае, решение будет принимать разработчик в каждом конкретном случае. А эти четыре пункта следует воспринимать скорее как совет, как мой ответ на вопрос "const — когда быть, когда не быть?" с уклоном в сторону "когда же ему все-таки не быть.".
А>P.S. Есть книги, которые посвящены подобным вопросам. ("Что и когда стоит или не стоит использовать в C++") Может стоит заглянуть в раздел сайта посвященным книгам по C++?
Давай конкретно, какие книги и ссылки ты бы мне советовал прочитать?
Здравствуйте, Дмитро, Вы писали:
Д>Здравствуйте, jazzer, Вы писали:
J>вот всякие кеширования и прочие временные и вспомогательные радости стоит объявлять mutable, ибо они не определяют состояние объекта, и, соответственно, не влияют на его константность. А дескриптор сокета, открытого временно и по случаю, не определяет состояние объекта, посему должен быть mutable.
Д>Пример. Допустим, мы разрабатываем класс, в котором хранятся параметры чего-либо. Каждый параметр имеет свой геттер (например, значение параметра value1 можно получить вызвав getValue1). Параметры могут, например, загружаться их файла.
Д>Члены-данные m_value1, m_value2 и прочее были объявлены mutable для того, чтобы в них можно было загрузить значения внутри геттера.
...код поскипан...
Д>Разве во второй реализации члены-данные m_value1, m_value2 и прочее не относятся к состоянию объекта? Что тогда к состоянию объекта этого класса относится?
То, что возвращается геттерами.
Твои члены-данные — это детали внутренней реализации, которые не должны беспокоить пользователя твоего класса в виде запрета вызова функций для константных объектов. Солбственно, это сказано в моем предыдущем посте в следующем выделенном абзаце
J>состояние объекта — это тоже деталь его интерфейса, хоть и не оформленная в виде метода. J>И если какой-то метод не должен менять состояние объекта, то это должно быть прописано в прототипе этого метода. J>А то, как это все устроено внутри объекта, не имеет никакого отношения к его интерфейсу вообще, и к константности методов, в частности.
Д>Модификтор const, на мой взгляд, играет две роли. Первая, он выполняет роль некого дискриминанта в интерфейсе класса. Он как бы в общем интерфейсе выделяет подинтерфейс, состоящий только их константных методов, которые не меняют состояние объекта. Вторая, он является указанием компилятору, чтобы тот предотвращал изменения членов-данных внутри константных методов. Как правило, изменение членов-данных приводит к изменению состояния объекта. В этом случае, эти две роли, являются взаимодополняющими. Однако члены-данные -- это не состояние. Они определяют его, но не являются им. Для класса "рациональных чисел" "две третьих" и "четыре шестых" -- может быть одним и тем же состоянием, пользователь класса может не иметь возможность в принципе их различать. Так же как и для "класса плоских углов меньших 360 градусов" 50 градусов и 410 -- это одно и тоже, хотя представлены они по разному. В недрах класса одно представление может переходить в другое (рациональное число может сокращаться, а угол приводиться к определенным пределам) не меняя при этом состояние объекта. А компилятор об этом не знает, в потому и ругается. А вместе с ним, ругается и разработчик класса, который ставит mutable или прибегает к иным приемам только для того, чтобы успокоить компилятор.
Согласен, в этом смысле есть некоторое неудобство.
Ведь мы хотим, чтобы в одних константных методах мутирующие члены могли изменяться. а в других — нет (быть "истинно константными"), и чтобы за этим всем следил компилятор. Единственное приемлемое решение — вынести все мутирующие члены во внутреннюю структуру и держать мутирующим только экземпляр этой структуры, а в "истинно константных" функциях объявлять константную ссылку на этот экземпляр и обращаться только через нее. Все это легко упаковывается в однострочный макрос (возможно, можно сделать такой шаблон), который пишется в начале каждой функции, желающей использовать мутирующие члены, с указанием желаемого доступа.
Д>Обсуждение этой темы как мне кажется, склонно перерасти в религиозную войну. Наверное, не может быть однозначного ответа, когда нужно ставить const, а когда нет. Решение, которое для одного является образцом совершенства, для другого может показаться уродливым и некрасивым. И наоборот. Я не думаю, что что-то сможет меня быстро переубедить. Равно как, далек от мысли, что я кого-то переубедил. Но в любом случае, готов выслушать любые мнения.
Нет, это — не религиозная война, это — очень серьезный вопрос.
(брошу пару копеек к твоим совершенно правильным словам).
Если внешняя, логическая константность (макросостояние) допускает изменения внутренние (микросостояния — говоря в терминах энтропии ),
то для этого в языке предусмотрены 2 механизма:
1) mutable члены-данные -- что логически означает их принадлежность к микросостоянию
2) развязка через указатели (константный указатель на неконстантные данные)
Здравствуйте, Anton V. Kolotaev, Вы писали:
AVK>Константностью метода GetValue1() автор класса Settings подчеркивает, что если не вызываются неконстантные методы класса, то последовательные вызовы GetValue1() будут давать одно и то же значение, которое с точки зрения клиента является частью состояния класса.
Бывает и псевдо-volatile — результат разный, но не из-за действий пользователя. Скажем, connection::is_open, проверяющий, не сдох ли канал.
Здравствуйте, Аноним, Вы писали:
А>довольно продолжительное время ломаю голову — когда делать функцию константной, а когда в этом нет необходимости???
А>объясните человеческим языком, пожалста.
Если она не должна менять обьект то она должна быть константной.
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, <Аноним>, Вы писали:
А>довольно продолжительное время ломаю голову — когда делать функцию константной, а когда в этом нет необходимости???
А>объясните человеческим языком, пожалста.
Константная функция-член не предполагает изменения внутреннего представления класса.
Если в теле этой функции ты попытаешься изменить внутренние переменные экземпляра класса, то компилятор не должен это пропустить.
Соответсвенно, если твоя функция изменяет какие-либо переменные объекта, то const не ставится, и наоборот.
... << RSDN@Home 1.0 beta 6a >>
Работать надо над собой...
const - когда быть, когда не быть
От:
Аноним
Дата:
23.04.03 05:44
Оценка:
приветствую, Вас!
довольно продолжительное время ломаю голову — когда делать функцию константной, а когда в этом нет необходимости???
Здравствуйте, Аноним, Вы писали:
А>довольно продолжительное время ломаю голову — когда делать функцию константной, а когда в этом нет необходимости???
Вообще-то ты можешь вообще не указывать этот самый конст, если не предполагается, что объекты твоего класса могут быть const. Дело в том, что константные функции нужны, для того чтобы работать константным экземпляром класса а также просто помогают лучше читать код.
Например.
class A {
bool m_bOk;
public:
bool IsOk() const {return m_bOk;}
bool IsOk() {return m_bOk;}
};
void f(const A& a)
{
a.IsOk(); //Здесь вызовется const версия иначе не откомпилится
}
Why do you call Visual Studio 'your bunny'?...
(c) one american colleague
Re: const - когда быть, когда не быть
От:
Аноним
Дата:
24.04.03 09:29
Оценка:
Здравствуйте, все кто ответил.
я понял так, поправте если не так, что определять когда следует использовать const исходя в первую очередь из обеспечения безопасности для объекта.
безопасность объекта — приоритет номер 1.
.................... — приоритет номер 2.
спасибо, Дмитро, приятно было услышать конструктивное прояснения ситуации, обязательно возьму на вооружение Ваш опыт.
спасибо всем.
Re[2]: const - когда быть, когда не быть
От:
Аноним
Дата:
25.04.03 18:12
Оценка:
Здравствуйте, Аноним, Вы писали:
А>я понял так, поправте если не так, что определять когда следует использовать const исходя в первую очередь из обеспечения безопасности для объекта.
Нет. Язык Си++ не имеет средств обеспечения безопасности. const — средство логического структурирования интерфейса.
Здравствуйте, Аноним, Вы писали:
А>Нет. Язык Си++ не имеет средств обеспечения безопасности. const — средство логического структурирования интерфейса.
Термин "безопасность" может использоваться в различных значениях. Для того, чтобы вызвать метод, не нужно передавать пароли. В этом смысле, C++, конечно не имеет средств обеспечения безопасности.
Но то, что компилятор пресекает попытки изменить константный объект, это, на мой взгляд, безусловно обеспечение безопасности. Но безопасности другого рода, безопасности от ошибок кодирования.
--
Дмитрий
--
Дмитрий
Re[4]: const - когда быть, когда не быть
От:
Аноним
Дата:
26.04.03 21:06
Оценка:
Здравствуйте, Дмитро, Вы писали:
А>Нет. Язык Си++ не имеет средств обеспечения безопасности. const — средство логического структурирования интерфейса.
Д>Термин "безопасность" может использоваться в различных значениях. Для того, чтобы вызвать метод, не нужно передавать пароли. В этом смысле, C++, конечно не имеет средств обеспечения безопасности.
Д>Но то, что компилятор пресекает попытки изменить константный объект, это, на мой взгляд, безусловно обеспечение безопасности. Но безопасности другого рода, безопасности от ошибок кодирования.
Вообразим себе Кремль. Где-то в Кремле кабинет тов. Сталина. Вокруг стена. Но охрана — только в воротах. Только в воротах спрашивают пропуск и проверяют, не несет ли кто тов. Сталину бомбу.
Это — "безопасность другого рода". "Другой род" этой самой безопасности заключается в том, что тов. Сталин защищен лишь от нечаянно (по забывчивости или невнимательности) пронесенной бомбы. Если кто хочет через стену (явно декларировав свои намерения), всегда пожалуйста.