![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Кусочек обсуждения const_cast в Клубе анонимных программистов.
Дійові особи:
WM - Вова Мутель
DB - Дима Бочоришвили
DS - Дима Шейко
VS - sinisterely yours
Исходный контент зверски порезан в соответствии с моим представлением о const в C++. Для полемики прошу обращаться к источникам дискуссии по ссылке выше.
WM: Я вижу три довода, зачем нужен const в C++
1. Анализируя модификатор const, компилятор может создать более эффективный/быстрый/правильный бинарный код программы или компилятор может оптимизировать сам процесс компиляции.
DB: Я в общем-то допускаю, что такое вполне реально. Однако я сам лично никогда не занимался исследованиями этого вопроса, а также не видел чьих-то исследований в этой области. Поэтому мне сложно судить насколько эта фича важная и какой реальный выигрышь она дает. Подозреваю что для enterprise систем не очень большой или даже незначительный. Возможно, что в случае каких-нибудь realtime или embedded приложений программист и сможет выжать еще несколько капель из программы, подставив в нужном
месте const.
Если у кого то есть ссылки на эту тему -- делитесь, интересно почитать.
Еще меня смущает, что я нигде в книжках по C++ не видел рекомендаций в плане "объявите виртуальную функцию как конст и она будет вызываться в 2 раза быстрее" или "константный указатель на класс занимает на 0.05% памяти меньше чем неконстантный". Где простой программист может получить эти сокровенные знания? Подкиньте пару примеров реальной выгоды такого
плана, если кто знает.
WM: 2. Возвращая или передавая наружу const указатель/ссылку, мы страхуемся от неожиданных манипуляций с нашим объектом.
DB: И это правда. Ну, по крайней мере, нам хочется верить в то, что никто не начнет использовать const_cast против нас ;) На мой взгляд - это ключевой момент зачем нужен const в C++. Этот подход является стандартным в языке и программистам действительно следует его придерживаться.
Но давайте вспомним, что в других языках можно решить эту проблему и без всяких const.
WM: a. передача переменных базовых типов в "чужие" функции
C++: void foo(const std::string&)
DB: Java & C#: в отличие от C++, в этих языках объекты базовых типов нельзя менять после их создания. Поэтому достаточно написать void
foo(String)
WM: b. передача коллекций наружу.
DB: В Java можно обернуть коллекцию в unmodifiable proxy или сделать копию
WM: с. передача объектов собственных классов
DB: Вот здесь чаще всего и происходят затыки у программистов на Java. Универсального рецепта я не знаю. Можно конечно побаловаться с unmodifiable proxy или делать копии, но эти решения имеют свою цену. Первое решение накладно для программиста, второе - не всегда возможно и требует дополнительной памяти и процессорного времени.
В заключение по этому пункту приведу цитату в моем вольном изложении.
Когда Андерса Хейлсберга спросили, почему в .Net нет модификатора const, он ответил, что программирование с использованием const напоминает плавание в воде - ты потихоньку плывешь в своем направлении, а от тебя все шире и шире расходятся волны. Когда программист пишет код с const'ами, ему все больше и больше необходимо, чтобы окружающий его код тоже использовал const грамотно. И единственная причина, почему можно использовать const на практике, это потому что можно в любой момент избавиться от const атрибута с помощью const_cast. С другой стороны, если бы Java или .Net ввели const в язык, то програмисты бы ожидали честный const, который бы нельзя было обойти.
WM: Так он имел в виду, что это плохие, ненужные волны ?
DB: Угу. Я не могу писать красивый код с const'ами, который использует чужой код с потерянными const'ами.
Типа я не могу написать комментарий к своей функции, если она вызывает другие функции без комментариев ;)
DB:
>>>> С другой стороны, когда я хочу использовать чью-то фунцию, то мне
>>>> нужно знать ее полный контракт, т.е. предусловия, постусловия. А
>>>> одного модифакотора const для описания контракта явно не достаточно.
WM:
>> Программирование с использование контрактов тоже ведь напоминает
>> плавание с кругами на воде ? :>
DB: А хз. Нет пока опыта в этом. Можно будет трек по Eiffel зарядить, чтобы ответить на этот вопрос.
WM: 3. Ну и последний довод - программа становится более читабельной.
DB: Да, становится, но намного ли?
Зачастую наличие/отсутствие const не несет дополнительной информации, если программист дал хорошие имена классу и методу.
Например, что ценного мне может дать const в объявлении функции class Employee
...
std::string getName() const;
?
Или же наооборот, отсутствие const при объявлении seter'ов?
С другой стороны, когда я хочу использовать чью-то фунцию, то мне нужно знать ее полный контракт, т.е. предусловия, постусловия. А одного модифакотора const для описания контракта явно недостаточно.
В заключение, моя позиция такова - когда я пишу на C++, я стараюсь использовать const. Если на Java или C# - я об отсутствии const не жалею.
...
DS: Боюсь, что это в принципе невозможно. volatile может влиять на генерацию кода, но не const.
Казалось бы, что компилятор в таком коде...
...мог бы выполнить оптимизацию и вынести вычисление размера за цикл,
например так:
Действительно, ведь объект c не изменяется нигде в данной функции (size тоже его не изменяет поскольку объявлен с const), почему тогда метод size() должен вызываться каждый раз в теле цикла -- он ведь будет возвращать одно и то же значение? Почему бы его вместо этого не вызвать всего один раз за пределами цикла? Вроде бы логично, но не тут-то было...
Во-первых, то, что в метод передается ссылка на константный объект говорит лишь о том, что этот метод не будет предпринимать попыток этот объект изменить, но это не значит, что объект будет оставаться неизменным на протяжении работы этого метода. Другой поток может изменить состояние данного объекта. А может и сам метод. Например, так:
Во-вторых, из-за присутствия в языке mutable и const_cast компилятор не может полагаться на то, что два подряд вызова size() вернут одно и тоже значение.
VS: volatile может влиять на генерацию кода, но не const.
const_cast приводит по стандарту к undefined behaviour. Т.е., возможно, и крэшу.
Пример возможного влияния const на генерацию кода:
Здесь не нужно вызывать copy constructor, можно пользоваться исходной копией snafu, т.е. передавать ссылку.
Разумеется, работает для случай, где программист сам не передаёт ссылку, но это м.б. нормально для value objects и т.п.
DS: Интересно. :)
А какой компилятор так оптимизирует? И что он делает с mutable полями snafu?
VS: Сферический в вакууме :)
Их не должно быть :) Либо foo() и bar() не должны дёргать код, их изменяющий. Последнее предположение - для single thread scope, как это по-русски? "Будет работать в контексте одного треда".
Добавлю, что круги на воде расходятся при столкновении "свежего" кода с legacy или proprietary. Обычно в виде тех самых печальных const_cast.
В конце концов, надо же это как-то публиковать более читабельно. И набивать контент в свой блог, а то уже замусорился повседневщиной до полной бессодержательности.
Дійові особи:
WM - Вова Мутель
DB - Дима Бочоришвили
DS - Дима Шейко
VS - sinisterely yours
Исходный контент зверски порезан в соответствии с моим представлением о const в C++. Для полемики прошу обращаться к источникам дискуссии по ссылке выше.
WM: Я вижу три довода, зачем нужен const в C++
1. Анализируя модификатор const, компилятор может создать более эффективный/быстрый/правильный бинарный код программы или компилятор может оптимизировать сам процесс компиляции.
DB: Я в общем-то допускаю, что такое вполне реально. Однако я сам лично никогда не занимался исследованиями этого вопроса, а также не видел чьих-то исследований в этой области. Поэтому мне сложно судить насколько эта фича важная и какой реальный выигрышь она дает. Подозреваю что для enterprise систем не очень большой или даже незначительный. Возможно, что в случае каких-нибудь realtime или embedded приложений программист и сможет выжать еще несколько капель из программы, подставив в нужном
месте const.
Если у кого то есть ссылки на эту тему -- делитесь, интересно почитать.
Еще меня смущает, что я нигде в книжках по C++ не видел рекомендаций в плане "объявите виртуальную функцию как конст и она будет вызываться в 2 раза быстрее" или "константный указатель на класс занимает на 0.05% памяти меньше чем неконстантный". Где простой программист может получить эти сокровенные знания? Подкиньте пару примеров реальной выгоды такого
плана, если кто знает.
WM: 2. Возвращая или передавая наружу const указатель/ссылку, мы страхуемся от неожиданных манипуляций с нашим объектом.
DB: И это правда. Ну, по крайней мере, нам хочется верить в то, что никто не начнет использовать const_cast против нас ;) На мой взгляд - это ключевой момент зачем нужен const в C++. Этот подход является стандартным в языке и программистам действительно следует его придерживаться.
Но давайте вспомним, что в других языках можно решить эту проблему и без всяких const.
WM: a. передача переменных базовых типов в "чужие" функции
C++: void foo(const std::string&)
DB: Java & C#: в отличие от C++, в этих языках объекты базовых типов нельзя менять после их создания. Поэтому достаточно написать void
foo(String)
WM: b. передача коллекций наружу.
DB: В Java можно обернуть коллекцию в unmodifiable proxy или сделать копию
WM: с. передача объектов собственных классов
DB: Вот здесь чаще всего и происходят затыки у программистов на Java. Универсального рецепта я не знаю. Можно конечно побаловаться с unmodifiable proxy или делать копии, но эти решения имеют свою цену. Первое решение накладно для программиста, второе - не всегда возможно и требует дополнительной памяти и процессорного времени.
В заключение по этому пункту приведу цитату в моем вольном изложении.
Когда Андерса Хейлсберга спросили, почему в .Net нет модификатора const, он ответил, что программирование с использованием const напоминает плавание в воде - ты потихоньку плывешь в своем направлении, а от тебя все шире и шире расходятся волны. Когда программист пишет код с const'ами, ему все больше и больше необходимо, чтобы окружающий его код тоже использовал const грамотно. И единственная причина, почему можно использовать const на практике, это потому что можно в любой момент избавиться от const атрибута с помощью const_cast. С другой стороны, если бы Java или .Net ввели const в язык, то програмисты бы ожидали честный const, который бы нельзя было обойти.
WM: Так он имел в виду, что это плохие, ненужные волны ?
DB: Угу. Я не могу писать красивый код с const'ами, который использует чужой код с потерянными const'ами.
Типа я не могу написать комментарий к своей функции, если она вызывает другие функции без комментариев ;)
DB:
>>>> С другой стороны, когда я хочу использовать чью-то фунцию, то мне
>>>> нужно знать ее полный контракт, т.е. предусловия, постусловия. А
>>>> одного модифакотора const для описания контракта явно не достаточно.
WM:
>> Программирование с использование контрактов тоже ведь напоминает
>> плавание с кругами на воде ? :>
DB: А хз. Нет пока опыта в этом. Можно будет трек по Eiffel зарядить, чтобы ответить на этот вопрос.
WM: 3. Ну и последний довод - программа становится более читабельной.
DB: Да, становится, но намного ли?
Зачастую наличие/отсутствие const не несет дополнительной информации, если программист дал хорошие имена классу и методу.
Например, что ценного мне может дать const в объявлении функции class Employee
...
std::string getName() const;
?
Или же наооборот, отсутствие const при объявлении seter'ов?
С другой стороны, когда я хочу использовать чью-то фунцию, то мне нужно знать ее полный контракт, т.е. предусловия, постусловия. А одного модифакотора const для описания контракта явно недостаточно.
В заключение, моя позиция такова - когда я пишу на C++, я стараюсь использовать const. Если на Java или C# - я об отсутствии const не жалею.
...
DS: Боюсь, что это в принципе невозможно. volatile может влиять на генерацию кода, но не const.
Казалось бы, что компилятор в таком коде...
void func(MyClass const &c) {
// . . .
for(int i = 0; i < c.size(); ++i) {
// . . .
}
// . . .
}
...мог бы выполнить оптимизацию и вынести вычисление размера за цикл,
например так:
void func(MyClass const &c) {
// . . .
for(int i = 0, n = c.size(); i < n; ++i) {
// . . .
}
// . . .
}
Действительно, ведь объект c не изменяется нигде в данной функции (size тоже его не изменяет поскольку объявлен с const), почему тогда метод size() должен вызываться каждый раз в теле цикла -- он ведь будет возвращать одно и то же значение? Почему бы его вместо этого не вызвать всего один раз за пределами цикла? Вроде бы логично, но не тут-то было...
Во-первых, то, что в метод передается ссылка на константный объект говорит лишь о том, что этот метод не будет предпринимать попыток этот объект изменить, но это не значит, что объект будет оставаться неизменным на протяжении работы этого метода. Другой поток может изменить состояние данного объекта. А может и сам метод. Например, так:
MyClass single;
//...
func(single, single);
void func(MyClass const &c, MyClass &d) {
// . . .
for(int i = 0, n = c.size(); i < n; ++i) {
d.append(c[i]); // здесь неявно изменяется и с
// . . .
}
// . . .
}
Во-вторых, из-за присутствия в языке mutable и const_cast компилятор не может полагаться на то, что два подряд вызова size() вернут одно и тоже значение.
VS: volatile может влиять на генерацию кода, но не const.
const_cast приводит по стандарту к undefined behaviour. Т.е., возможно, и крэшу.
Пример возможного влияния const на генерацию кода:
static void foo(const SomeClass a);
static void bar(const SomeClass a);
void foobar()
{
SomeClass snafu;
foo(snafu);
bar(snafu);
}
Здесь не нужно вызывать copy constructor, можно пользоваться исходной копией snafu, т.е. передавать ссылку.
Разумеется, работает для случай, где программист сам не передаёт ссылку, но это м.б. нормально для value objects и т.п.
DS: Интересно. :)
А какой компилятор так оптимизирует? И что он делает с mutable полями snafu?
VS: Сферический в вакууме :)
Их не должно быть :) Либо foo() и bar() не должны дёргать код, их изменяющий. Последнее предположение - для single thread scope, как это по-русски? "Будет работать в контексте одного треда".
Добавлю, что круги на воде расходятся при столкновении "свежего" кода с legacy или proprietary. Обычно в виде тех самых печальных const_cast.
В конце концов, надо же это как-то публиковать более читабельно. И набивать контент в свой блог, а то уже замусорился повседневщиной до полной бессодержательности.
Tags:
(no subject)
25/1/06 11:54 (UTC)1)определения копирующего конструктора для SomeClass и его всех функций, прямо или косвенно использующихся в функциях foo и bar, видимы в данной точке
2)эти функции достаточно просты и не имеют побочного действия
(no subject)
26/1/06 01:37 (UTC)Разумеется, работает для случая, когда программист сам не передаёт ссылку, но это м.б. нормально для value objects и т.п.
1) для value object всегда есть копирующий конструктор; к.к. по умолчанию тоже подойдёт.
2) побочные эффекты возможны, просто их дерево вызовов не должно цеплять передаваемые параметры.
3) я не говорю, что эта оптимизация мега-полезна :) Но она возможна и имеет право на существование. Нужно было всего лишь привести пример влияния const на результат компиляции.
хотелось бы добавить
28/1/06 11:10 (UTC)смысл использования std::string getName() const;
на первый взгляд плюсов нету, но ты сможешь вызвать эту функцию
из другой, константной, с более сложным названием,
по которой без const непонятно, что она константная.
2.
а вот например такой код
....много кода (1)
const int length = someObject.getLength();
otherObject1.someMethod1(length);
....еще много кода (2)
otherObject2.someMethod2(length);
....еще много кода (3)
otherObject3.someMethod3(length);
тут я употреблю const рядом с локальной переменной.
в этом случае у программиста работающего с чужим кодом после перехода к определению переменной невозникает никаких вопросов меняется ли
значение length между вызовами или нет, и в куче кода не надо
просматривать все случаи ее сиспользования чтобы узнать
модифицируется она или нет.