const - когда быть, когда не быть
От: Аноним  
Дата: 23.04.03 05:44
Оценка:
приветствую, Вас!

довольно продолжительное время ломаю голову — когда делать функцию константной, а когда в этом нет необходимости???

объясните человеческим языком, пожалста.
Re: const - когда быть, когда не быть
От: WolfHound  
Дата: 23.04.03 05:55
Оценка: +1
Здравствуйте, Аноним, Вы писали:

А>довольно продолжительное время ломаю голову — когда делать функцию константной, а когда в этом нет необходимости???


А>объясните человеческим языком, пожалста.

Если она не должна менять обьект то она должна быть константной.
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re: const - когда быть, когда не быть
От: A_Gura Россия http://a-gura.livejournal.com
Дата: 23.04.03 06:00
Оценка: +1
Здравствуйте, <Аноним>, Вы писали:

А>довольно продолжительное время ломаю голову — когда делать функцию константной, а когда в этом нет необходимости???


А>объясните человеческим языком, пожалста.


Константная функция-член не предполагает изменения внутреннего представления класса.
Если в теле этой функции ты попытаешься изменить внутренние переменные экземпляра класса, то компилятор не должен это пропустить.

Соответсвенно, если твоя функция изменяет какие-либо переменные объекта, то const не ставится, и наоборот.
... << RSDN@Home 1.0 beta 6a >>
Работать надо над собой...
Re: const - когда быть, когда не быть
От: creatio Украина  
Дата: 23.04.03 10:45
Оценка:
Здравствуйте, Аноним, Вы писали:

А>довольно продолжительное время ломаю голову — когда делать функцию константной, а когда в этом нет необходимости???


Вообще-то ты можешь вообще не указывать этот самый конст, если не предполагается, что объекты твоего класса могут быть 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[2]: const - когда быть, когда не быть
От: Дмитро  
Дата: 23.04.03 12:10
Оценка: 11 (2) -5
Здравствуйте, 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:
   int mutable 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 желательно использовать при объявлении членов-данных, которые не должны меняться на протяжении всей жизни объекта.

--
Дмитрий
--
Дмитрий
Re[3]: const - когда быть, когда не быть
От: Аноним  
Дата: 23.04.03 14:15
Оценка: 12 (3) +1
Здравствуйте, Дмитро, Вы писали:

[skip]

Д>На самом-то деле вопрос гораздо сложнее, чем кажется. Бездумное использование этого модификатора (всовывание его всюду, где только можно), способно создать немало неприятностей.


Бездумное программирование вообще создаёт много неприятностей

Д>Пример. Допустим, у нас есть некий класс A, от которого в принцие можно наследоваться (т.е. не final и не sealed), с методом doSomething. Реализация этого метода в этом же классе не меняет члены данных. Стоит ли объявить ее const? Предположим, мы так и сделали.

Д>
Д>class A {
Д>private:
Д>   int i;
Д>public:
Д>   virtual void doSomething() const {
Д>      printf("%d\n", i);
Д>   }
Д>};
Д>

Д>Пока все замечательно.

Напротив, очень даже странно, функция doSomething() ни входных, ни выходных параметров не имеет, возвращаемого значения тоже нет и объект не меняет. Что же она должна делать? (Только писать в std::cout "Hello world!"?! )
Короче "const" тут сразу под большим вопросом.

Д>Далее пишется отпределенное количество кода, ... В общем, хорошо.


Д>И вдруг, нам понадобилось создать класс B унаследованный от A и переопределить в нем doSomething. И тут оказывается, что B::doSomething меняет члены данных класса B, поэтому не может быть константным!


А мне кажется странным, что у нескольких классов до этого функция doSomething была константной, а у нового класса эта функция оказывается неконстантной. Может быть новый класс делает в doSomething чего не должен делать? А может...

Д>Поэтому, я считаю, что если есть хоть какое-то сомнение в необходимости constа, что лучше его не использовать.

Не формализируемо, а посему неприменимо

Д>Исходя из своего опыта, могу сказать, что в большинстве случаев: <...>


P.S. Есть книги, которые посвящены подобным вопросам. ("Что и когда стоит или не стоит использовать в C++") Может стоит заглянуть в раздел сайта посвященным книгам по C++?

Удалено избыточное цитирование. -- ПК.
Re[4]: const - когда быть, когда не быть
От: Дмитро  
Дата: 24.04.03 03:16
Оценка: 10 (1)
Здравствуйте, Аноним, Вы писали:

А>Бездумное программирование вообще создаёт много неприятностей


Не стоит обобщать мои слова доводя их до банальности. Далее о тексту я привожу пример и разъясняю свои точку зрения.

А>Напротив, очень даже странно, функция 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++?


Давай конкретно, какие книги и ссылки ты бы мне советовал прочитать?

--
Дмитрий
--
Дмитрий
Re[5]: const - когда быть, когда не быть
От: jazzer Россия Skype: enerjazzer
Дата: 24.04.03 08:20
Оценка: 5 (2) +2
Здравствуйте, Дмитро, Вы писали:

Д>Исходя из своего опыта, могу сказать, что в большинстве случаев:


Д>1. const никогда не следует использовать в объявлении методов интерфейсных классов.

А>Не согласен у методов аля "Get", модификатор const должен быть в подавляющем большинстве случаев.

Д>Методы аля "Get" могут возвращать данные, которые могут и не храниться непостредственно в объекте. Они могут читать из файла, делать запросы по сети, устанавливать и разрывать при этом соединения, кешировать эти данные наконец. При этом, возможно, что-то в классе и поменяется. Но пользователю класса нужно только получить эти данные. И его не должно беспокоить каким образом они были получены. Его не должно беспокоить, поменялся ли какой-нибудь внутренний указатель или дескриптор сокета, к которому он все равно доступа, может быть, никогда и не получит. Как получить эти данные -- забота разработчика класса, и я считаю, что он, в свою очередь, не должен заботиться о том, как избавиться от нежелательной константности. Кстати, ты не рискнул сказать "во всех случаях", ты сказал мягче "в подавляющем большинстве случаев". Полагаю, ты сталкивался с теми случаями, когда все же не нужно было делать метод константным.


вот всякие кеширования и прочие временные и вспомогательные радости стоит объявлять mutable, ибо они не определяют состояние объекта, и, соответственно, не влияют на его константность. А дескриптор сокета, открытого временно и по случаю, не определяет состояние объекта, посему должен быть mutable.

Д>Модификатор const в данном случае навязывает разработчику классов (наследующихся от заданных интерфейсных) некое внутреннее представление и, как следствие, нарушает инкапсуляцию.

А>Не совсем понял.
А>Во-первых сам интерфейс есть некое навязывание. (Не хочешь навязывания, есть замечательный () void *, а также возможность сериализовать объект в поток байт )
А>Во-вторых каким это образом const разрушает инкапсуляцию!?

Д>Интерфейс не навязывает внутреннее представление. Внутренее представление может варьироваться не меняя при этом интерфейса. Собственно, в этом и назначение интерфейса -- оградить пользователя класса от его (класса) внутренностей.

Д>Однако, если я объявляю метод константными, то я подразумеваю, что состояние объекта не будет меняться. Т.е. из всех возможных реализаций заданного класса я отказываюсь от тех, которые меняют состояние (конечно, могу и не отказаться воспользовавшись приемами устранения нежелательной константности). Именно это я подразумевал, когда говорил, что const нарушает инкапсуляцию (кстати, именно нарушает, а не разрушает). Модификатор const навязывает какую-то определенную реализацию. Причем, привязка к реализации делается слишком рано -- на этапе проектирования интерфейсов.

состояние объекта — это тоже деталь его интерфейса, хоть и не оформленная в виде метода.
И если какой-то метод не должен менять состояние объекта, то это должно быть прописано в прототипе этого метода.
А то, как это все устроено внутри объекта, не имеет никакого отношения к его интерфейсу вообще, и к константности методов, в частности.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: const - когда быть, когда не быть
От: Аноним  
Дата: 24.04.03 09:29
Оценка:
Здравствуйте, все кто ответил.
я понял так, поправте если не так, что определять когда следует использовать const исходя в первую очередь из обеспечения безопасности для объекта.
безопасность объекта — приоритет номер 1.
.................... — приоритет номер 2.
спасибо, Дмитро, приятно было услышать конструктивное прояснения ситуации, обязательно возьму на вооружение Ваш опыт.

спасибо всем.
Re[2]: const - когда быть, когда не быть
От: jazzer Россия Skype: enerjazzer
Дата: 24.04.03 09:54
Оценка: +2
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, все кто ответил.

А>я понял так, поправте если не так, что определять когда следует использовать const исходя в первую очередь из обеспечения безопасности для объекта.
А>безопасность объекта — приоритет номер 1.
А>.................... — приоритет номер 2.
А>спасибо, Дмитро, приятно было услышать конструктивное прояснения ситуации, обязательно возьму на вооружение Ваш опыт.

А>спасибо всем.


Нет, не безопасности объекта, а ясного выражения того, что представляют из себя класс и его методы.
Безопасность лишь вытекает отсюда.

например, в любом классе функция dump(), распечатывающая состояние объекта, просто обязана быть константной, потому что простой дамп не может изменить состояние объекта. А если ты для обеспечения быстрого дампа завел строчку, в которой этот дамп лежит, и при каждом вызове dump() проверяешь, изменился ли объект (у тебя есть для этого соответствующий флаг, например), и, если он не изменился, просто возвращаешь эту строчку, а если изменился, создаешь новую, ее возвращаешь и флаг валидности внутренней строчки с дампом сбрасываешь в false, то у тебя появляются по крайней мере 2 сущности, которые будут в этом методе изменяться, тем не менее, они не определяют состояние объекта, интересное пользователям этого класса (т.е. доступное через соответствующие публичные члены: они должны возвращать одно и тоже и до дампа, и после) — ты должен сделать эти 2 сущности mutable, но оставить функцию константной.

Так что, определяя, должна ли быть функция константной, смотри на это с точки зрения константности обекта в терминах доступных возвращаемых публичным интерфейсом значений (да и просто по сути, по логике и исходя из здравого смысла ^) )
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[6]: const - когда быть, когда не быть
От: Дмитро  
Дата: 24.04.03 10:52
Оценка: 10 (1) -1
Здравствуйте, 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, а когда нет. Решение, которое для одного является образцом совершенства, для другого может показаться уродливым и некрасивым. И наоборот. Я не думаю, что что-то сможет меня быстро переубедить. Равно как, далек от мысли, что я кого-то переубедил. Но в любом случае, готов выслушать любые мнения.

--
Дмитрий
--
Дмитрий
Re[7]: const - когда быть, когда не быть
От: Anton V. Kolotaev  
Дата: 24.04.03 11:10
Оценка: 12 (2) +2
Здравствуйте, Дмитро, Вы писали:

Д>Разве во второй реализации члены-данные m_value1, m_value2 и прочее не относятся к состоянию объекта? Что тогда к состоянию объекта этого класса относится?


Состояние объекта — это то, что ожидает получить клиент класса.

void f(Settings *settings) {
    int v1 = settings->GetValue1();
    int v2 = settings->GetValue1();
    //   v1 == v2!!!
}


Константностью метода GetValue1() автор класса Settings подчеркивает, что если не вызываются неконстантные методы класса, то последовательные вызовы GetValue1() будут давать одно и то же значение, которое с точки зрения клиента является частью состояния класса.
Re[7]: const - когда быть, когда не быть
От: jazzer Россия Skype: enerjazzer
Дата: 24.04.03 11:20
Оценка: 6 (1)
Здравствуйте, Дмитро, Вы писали:

Д>Здравствуйте, jazzer, Вы писали:


J>вот всякие кеширования и прочие временные и вспомогательные радости стоит объявлять mutable, ибо они не определяют состояние объекта, и, соответственно, не влияют на его константность. А дескриптор сокета, открытого временно и по случаю, не определяет состояние объекта, посему должен быть mutable.


Д>Пример. Допустим, мы разрабатываем класс, в котором хранятся параметры чего-либо. Каждый параметр имеет свой геттер (например, значение параметра value1 можно получить вызвав getValue1). Параметры могут, например, загружаться их файла.


Д>Члены-данные m_value1, m_value2 и прочее были объявлены mutable для того, чтобы в них можно было загрузить значения внутри геттера.


...код поскипан...

Д>Разве во второй реализации члены-данные m_value1, m_value2 и прочее не относятся к состоянию объекта? Что тогда к состоянию объекта этого класса относится?


То, что возвращается геттерами.
Твои члены-данные — это детали внутренней реализации, которые не должны беспокоить пользователя твоего класса в виде запрета вызова функций для константных объектов. Солбственно, это сказано в моем предыдущем посте в следующем выделенном абзаце

J>состояние объекта — это тоже деталь его интерфейса, хоть и не оформленная в виде метода.

J>И если какой-то метод не должен менять состояние объекта, то это должно быть прописано в прототипе этого метода.
J>А то, как это все устроено внутри объекта, не имеет никакого отношения к его интерфейсу вообще, и к константности методов, в частности.

Д>Модификтор const, на мой взгляд, играет две роли. Первая, он выполняет роль некого дискриминанта в интерфейсе класса. Он как бы в общем интерфейсе выделяет подинтерфейс, состоящий только их константных методов, которые не меняют состояние объекта. Вторая, он является указанием компилятору, чтобы тот предотвращал изменения членов-данных внутри константных методов. Как правило, изменение членов-данных приводит к изменению состояния объекта. В этом случае, эти две роли, являются взаимодополняющими. Однако члены-данные -- это не состояние. Они определяют его, но не являются им. Для класса "рациональных чисел" "две третьих" и "четыре шестых" -- может быть одним и тем же состоянием, пользователь класса может не иметь возможность в принципе их различать. Так же как и для "класса плоских углов меньших 360 градусов" 50 градусов и 410 -- это одно и тоже, хотя представлены они по разному. В недрах класса одно представление может переходить в другое (рациональное число может сокращаться, а угол приводиться к определенным пределам) не меняя при этом состояние объекта. А компилятор об этом не знает, в потому и ругается. А вместе с ним, ругается и разработчик класса, который ставит mutable или прибегает к иным приемам только для того, чтобы успокоить компилятор.


Согласен, в этом смысле есть некоторое неудобство.
Ведь мы хотим, чтобы в одних константных методах мутирующие члены могли изменяться. а в других — нет (быть "истинно константными"), и чтобы за этим всем следил компилятор. Единственное приемлемое решение — вынести все мутирующие члены во внутреннюю структуру и держать мутирующим только экземпляр этой структуры, а в "истинно константных" функциях объявлять константную ссылку на этот экземпляр и обращаться только через нее. Все это легко упаковывается в однострочный макрос (возможно, можно сделать такой шаблон), который пишется в начале каждой функции, желающей использовать мутирующие члены, с указанием желаемого доступа.

Д>Обсуждение этой темы как мне кажется, склонно перерасти в религиозную войну. Наверное, не может быть однозначного ответа, когда нужно ставить const, а когда нет. Решение, которое для одного является образцом совершенства, для другого может показаться уродливым и некрасивым. И наоборот. Я не думаю, что что-то сможет меня быстро переубедить. Равно как, далек от мысли, что я кого-то переубедил. Но в любом случае, готов выслушать любые мнения.


Нет, это — не религиозная война, это — очень серьезный вопрос.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
const - когда быть, когда не быть
От: Андрей Тарасевич Беларусь  
Дата: 24.04.03 18:00
Оценка: 257 (25) +3
#Имя: FAQ.cpp.const
А>довольно продолжительное время ломаю голову — когда делать функцию константной, а когда в этом нет необходимости???

Однозначного "механического" ответа на этот вопрос не существует. Зачастую в качестве "механического" ответа на этот вопрос приводят что-то вроде "если функция не меняет содержимого класса, то ее следует сделать константной, в противном случае — неконстантной", где под словом "меняет" подразумевается фактическое изменение одного их внутренних полей класса. Во многих случаях это правило дает правильный (или почти правильный) ответ на поставленный вопрос. Но на самом деле в общем случае это правило является лишь простейшим первым приближением к правильному ответу — оно реализует идею физической константности. На самом делен язык С++ более-менее приспособлен к реализации идеи логической констаности. Делать ли метод класса константым или нет — вопрос, который должен решаться на уровне дизайна класса и проекта.

У твоего класса есть некоторое состояние, которое видно извне через интерфейс этого класса. Константными должны быть методы, которые не меняют этого состояния. Соответственно, с модификатором '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' в объявлениях методов, параметров, объектов и т.д. в процесе реализации.

Внесены изменения автора. -- ПК.
Best regards,
Андрей Тарасевич
Re[2]: const - когда быть, когда не быть
От: Кодт Россия  
Дата: 24.04.03 18:19
Оценка: 1 (1)
Здравствуйте, Андрей Тарасевич, Вы писали:

<>

(брошу пару копеек к твоим совершенно правильным словам).

Если внешняя, логическая константность (макросостояние) допускает изменения внутренние (микросостояния — говоря в терминах энтропии ),
то для этого в языке предусмотрены 2 механизма:
1) mutable члены-данные -- что логически означает их принадлежность к микросостоянию
2) развязка через указатели (константный указатель на неконстантные данные)
... << RSDN@Home 1.0 beta 6a >>
Перекуём баги на фичи!
Re[2]: В Q&A
От: WolfHound  
Дата: 24.04.03 18:43
Оценка: 1 (1) +7
Здравствуйте, Андрей Тарасевич, Вы писали:

Сабж.
... << RSDN@Home 1.0 beta 5 >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[8]: const - когда быть, когда не быть
От: desperado_gmbh http://www.livejournal.com/users/tolstopuz
Дата: 25.04.03 09:16
Оценка: 1 (1)
Здравствуйте, Anton V. Kolotaev, Вы писали:

AVK>Константностью метода GetValue1() автор класса Settings подчеркивает, что если не вызываются неконстантные методы класса, то последовательные вызовы GetValue1() будут давать одно и то же значение, которое с точки зрения клиента является частью состояния класса.


Бывает и псевдо-volatile — результат разный, но не из-за действий пользователя. Скажем, connection::is_open, проверяющий, не сдох ли канал.
Re[2]: const - когда быть, когда не быть
От: Аноним  
Дата: 25.04.03 18:12
Оценка:
Здравствуйте, Аноним, Вы писали:

А>я понял так, поправте если не так, что определять когда следует использовать const исходя в первую очередь из обеспечения безопасности для объекта.


Нет. Язык Си++ не имеет средств обеспечения безопасности. const — средство логического структурирования интерфейса.
Re[3]: const - когда быть, когда не быть
От: Дмитро  
Дата: 26.04.03 01:41
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Нет. Язык Си++ не имеет средств обеспечения безопасности. const — средство логического структурирования интерфейса.


Термин "безопасность" может использоваться в различных значениях. Для того, чтобы вызвать метод, не нужно передавать пароли. В этом смысле, C++, конечно не имеет средств обеспечения безопасности.

Но то, что компилятор пресекает попытки изменить константный объект, это, на мой взгляд, безусловно обеспечение безопасности. Но безопасности другого рода, безопасности от ошибок кодирования.

--
Дмитрий
--
Дмитрий
Re[4]: const - когда быть, когда не быть
От: Аноним  
Дата: 26.04.03 21:06
Оценка:
Здравствуйте, Дмитро, Вы писали:

А>Нет. Язык Си++ не имеет средств обеспечения безопасности. const — средство логического структурирования интерфейса.


Д>Термин "безопасность" может использоваться в различных значениях. Для того, чтобы вызвать метод, не нужно передавать пароли. В этом смысле, C++, конечно не имеет средств обеспечения безопасности.


Д>Но то, что компилятор пресекает попытки изменить константный объект, это, на мой взгляд, безусловно обеспечение безопасности. Но безопасности другого рода, безопасности от ошибок кодирования.


Вообразим себе Кремль. Где-то в Кремле кабинет тов. Сталина. Вокруг стена. Но охрана — только в воротах. Только в воротах спрашивают пропуск и проверяют, не несет ли кто тов. Сталину бомбу.

Это — "безопасность другого рода". "Другой род" этой самой безопасности заключается в том, что тов. Сталин защищен лишь от нечаянно (по забывчивости или невнимательности) пронесенной бомбы. Если кто хочет через стену (явно декларировав свои намерения), всегда пожалуйста.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.