Здравствуйте, Jenyay, Вы писали:
ПК>> А факт нерегулярности таких подсказок не наводит на мысль, что ПК>> что-то не так с выбранным стилем?
J> Вот поэтому я и стараюсь не использовать ссылок. А, впринципе, как J> тогда быть (всмысле регулярности)?
Дык, нерегулярность обусловлена не одновременным использованием ссылок и указателей,
а отсутствием & в некоторых случаях:
void f(int*); // указатель как-будто для того, чтобы потом в коде было "видно", что аргумент модифицируетсяint* g();
void h(const int*); // ничего не меняет, но принимает указатель по другим причинамint* p;
int main()
{
int i;
f(&i); // ага, "видно" :-)
f(p); // тому, кто привык видеть & при модификации, грустно: здесь этого нет
f(g()); // и здесь тоже
h(&i); // а здесь, вообще, "обман": эта функция ничего не меняет...
}
J> Какой тогда стиль использовать?
Я остановился на более-менее простой "системе": если аргумент может быть NULL,
передаем указатель, если нет — ссылку. That simple
Posted via RSDN NNTP Server 1.6 RC1
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, WolfHound, Вы писали:
WH>Да ну брось. Если функция меняет параметры то это должно быть отражено в ее имени, а не в ее параметрах.
Должно, но кто знает, на сколько порядочный окажется тот, кто обзывал функцию. К тому же сразу может быть не понятно, какое значение меняется, например,
void SetValue (int &n);
не ясно, устанавливается параметр какой-то внутренний (если это метод класса) или n. Только по смыслу надо смотреть.
Вот у Голуба, наверное, лучше написано:
120. Ссылочные аргументы всегда должны быть константами
121. Никогда не используйте ссылки в качестве результатов, пользуйтесь указателями
Использование ссылочных аргументов в языке программирования вызвано четырьмя причинами:
· Они нужны вам для определения конструктора копии.
· Они нужны вам для определения перегруженных операций. Если вы определили:
some_class *operator+( some_class *left, some_class *right );
то вы должны сделать такое дополнение:
some_class x, y;
x = *(&x + &y)
Использование ссылок для аргумента и возвращаемого значения позволяет вам написать:
x = x + 1;
· Вы часто хотите передать объекты по значению, исходя из логики. Например, вы обычно в функцию передаете тип double, а не указатель на double. Тем не менее, тип double представляет собой 8-байтовую упакованную структуру с тремя полями: знаковым битом, мантиссой и порядком. Передавайте в этой ситуации ссылку на константный объект.
· Если объект какого-нибудь определенного пользователем класса обычно передается по значению, то используйте вместо этого ссылку на константный объект, чтобы избежать неявного вызова конструктора копии.
Ссылки в языке не предназначены для имитации Паскаля и не должны использоваться так, как используются в программе на Паскале.
Проблема ссылочных аргументов — сопровождение. В прошлом году один из наших сотрудников написал следующую подпрограмму:
void copy_word( char *target, char *&src ) // src является
// ссылкой на char*
{
while( isspace(*src) )
++src; // Инкрементировать указатель,
// на который ссылается src.
while( *src && !isspace(*src) )
*target++ = *src++; // Передвинуть указатель,
// на который ссылается src,
// за текущее слово.
}
Автор полагал, что вы будете вызывать copy_word() многократно. Каждый раз подпрограмма копировала бы следующее слово в буфер target и продвигала бы указатель в источнике.
Вчера вы написали следующий код:
f( const char *p )
{
char *p = new char[1024];
load( p );
char word[64];
copy_word( word, p );
delete( p ); // Сюрприз! p был модифицирован, поэтому весь
} // этот участок памяти обращается в кучу мусора!
Главная проблема состоит в том, что, глядя на вызов copy_word( word, p ), вы не получаете подсказки о возможном изменении p в подпрограмме. Чтобы добраться до этой информации, вы должны взглянуть на прототип этой функции (который, вероятно, скрыт на 6-ом уровне вложенности в заголовочном файле). Огромные проблемы при сопровождении.
Если что-то похоже на обычный вызов функции Си, то оно должно и действовать как вызов обычной функции Си. Если бы автор copy_word() использовал указатель для второго аргумента, то вызов выглядел бы подобным образом:
copy_word( word, &p );
Этот дополнительный знак & является решающим. Средний сопровождающий программист полагает, что единственная причина передачи адреса локальной переменной в другую функцию состоит в том, чтобы разрешить функции модифицировать эту локальную переменную. Другими словами, вариант с указателем является самодокументирующимся; вы сообщаете своему читателю, что этот объект изменяется функцией. Ссылочный аргумент не дает вам такой информации.
Это не значит, что вы должны избегать ссылок. Четвертая причина в начале этого раздела вполне законна: ссылки являются замечательным способом избегать ненужных затрат на копирование, неявных при передаче по значению. Тем не менее, для обеспечения безопасности ссылочные аргументы должны всегда ссылаться на константные объекты. Для данного прототипа:
f( const some_class &obj );
этот код вполне законен:
some_class an_object;
f( an_object );
Он похож на вызов по значению и при этом, что более важно, действует подобно вызову по значению — модификатор const предотвращает модификацию an_object в функции f(). Вы получили эффективность вызова по ссылке без его проблем.
Подведем итог: Я решаю, нужно или нет использовать ссылку, вначале игнорируя факт существования ссылок. Входные аргументы функций передаются по значению, а выходные — используют указатели на то место, где будут храниться результаты. Я затем преобразую те аргументы, которые передаются по значению, в ссылки на константные объекты, если эти аргументы:
· являются объектами какого-то класса (в отличие от основных типов, подобных int);
· не модифицируются где-то внутри функции.
Объекты, которые передаются по значению и затем модифицируются внутри функции, конечно должны по-прежнему передаваться по значению.
В заключение этого обсуждения рассмотрим пример из реальной жизни того, как не надо использовать ссылки. Объект CDocument содержит список объектов CView. Вы можете получить доступ к элементам этого списка следующим образом:
CDocument *doc;
CView *view;
POSITION pos = doc->GetFirstViewPosition();
while( view = GetNextView(pos) )
view->Invalidate();
Здесь есть две проблемы. Во-первых, у функции GetNextView() неудачное имя. Она должна быть названа GetCurrentViewAndAdvancePosition(), потому что она на самом деле возвращает текущий элемент и затем продвигает указатель положения (который является ссылочным аргументом результата) на следующий элемент. Что приводит нас ко второй проблеме: средний читатель смотрит на предыдущий код и задумывается над тем, как завершается этот цикл. Другими словами, здесь скрывается сюрприз. Операция итерации цикла скрыта в GetNextView(pos), поэтому неясно, где она происходит. Ситуация могла быть хуже, если бы цикл был больше и содержал бы несколько функций, использующих pos в качестве аргумента — вы бы не имели никакого представления о том, какая из них вызывает перемещение.
Есть множество лучших способов решения этой проблемы. Простейший заключается в использовании в качестве аргумента GetNextView() указателя вместо ссылки:
POSITION pos = doc->GetFirstViewPosition();
while( p = GetNextView( &pos ) )
p->Invalidate();
Таким способом &pos сообщает вам, что pos будет модифицироваться; иначе зачем передавать указатель? Тем не менее, существуют и лучшие решения. Вот первое:
for( CView *p = doc->GetFirstView(); p ; p = p->NextView() )
p->Invalidate();
Вот второе:
POSITION pos = doc->GetFirstViewPosition();
for( ; pos ; pos = doc->GetNextView(pos) )
(pos->current())->Invalidate();
Вот третье:
CPosition pos = doc->GetFirstViewPosition();
for( ; pos; pos.Advance() )
( pos->CurrentView() )->Invalidate();
Вот четвертое:
ViewListIterator cur_view = doc->View_list(); // Просмотреть
// весь список
// отображений
// этого
// документа.
for( ; cur_view ; ++cur_view ) // ++ переходит к следующему
// отображению.
cur_view->Invalidate(); // -> возвращает указатель View*.
Вероятно, есть еще дюжина других возможностей. Все предыдущее варианты обладают требуемым свойством — в них нет скрытых операций и ясно, как происходит переход к "текущему положению".
Здравствуйте, Flamer, Вы писали:
F>Ну не совсем так, по-моему... Все-же от параметров многое зависит. Сравните: F>
F>void Some(CClass& class1);
F>// и
F>void Some(const CClass& class1);
F>
F>Второй прототип функции ясно говорит, что переданный по ссылке объект изменить нельзя. Хотя, конечно, взять за правило, что если функция что-то меняет — надо добавлять префикс Set — это никогда не помешает.
Я не много не о том например есть swap есть два варианта его реализации
Первый вариант ИМХО много лучше. Мало того что во втором случае придется писать
A a1;
A a2;
...
swap(&a1, &a2);
что не очень удобно.
Но что еще хуже можно забытсься и написать
A* a1;
A* a2;
...
swap(a1, a2);
И в место очень дешовой (и интуитивно подразумевавшейся) операции обмена указателей мы получим болие дорогую обмен содержимого объектов.
А в других случаях возможны не понятные рантайм глюки которые будешь очень долго искать.
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, <Аноним>, Вы писали:
А>А зачем? И чем указатели отличаются от ссылок?
Например, чтобы функция могла изменять указатель. А ссылки плохи тем, что ты можешь не заметить, что функция меняет значение параметра, а если сам передаешь указатель, то к этому должен быть готов.
А то кто задавал вопрос, видел как передаются параметры командной строки в функцию main() ?
Зачем
От:
Аноним
Дата:
07.08.03 21:43
Оценка:
нужны указатели на указатели?
В принципе?
Re[2]: Зачем
От:
Аноним
Дата:
08.08.03 00:20
Оценка:
Здравствуйте, size_t, Вы писали:
>> нужны указатели на указатели? >> В принципе? _>Еще как! Особенно указатели на ссылки на двухмерные массивы указателей!
Здравствуйте, Jenyay, Вы писали:
J>Например, чтобы функция могла изменять указатель. А ссылки плохи тем, что ты можешь не заметить, что функция меняет значение параметра, а если сам передаешь указатель, то к этому должен быть готов.
Да ну брось. Если функция меняет параметры то это должно быть отражено в ее имени, а не в ее параметрах.
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
WH>Здравствуйте, Jenyay, Вы писали:
J>>Например, чтобы функция могла изменять указатель. А ссылки плохи тем, что ты можешь не заметить, что функция меняет значение параметра, а если сам передаешь указатель, то к этому должен быть готов.
WH>Да ну брось. Если функция меняет параметры то это должно быть отражено в ее имени, а не в ее параметрах.
Ну не совсем так, по-моему... Все-же от параметров многое зависит. Сравните:
Второй прототип функции ясно говорит, что переданный по ссылке объект изменить нельзя. Хотя, конечно, взять за правило, что если функция что-то меняет — надо добавлять префикс Set — это никогда не помешает.
Здравствуйте, <Аноним>, Вы писали:
А>нужны указатели на указатели? А>В принципе?
На этот вопрос одновременно сложно и просто ответить.
Встречный вопрос — "Зачем нужны переменные? В принципе?"
Как и переменные хранят данные, в часности адрес... указывающий на переменные которые хранят адрес...
Если где это ипользуется, то:
виртуальная таблица указателей на функции в классах реализуется имеено так...
есть указатель на массив указателей на функции... т.е. указатель на указатели...
примеров может быть множество...
Вобщем это требуется в основном тогда когда нужен массив элементов, размер которого неизвестен и неизвестно о существании этих объектов, или важны их адреса (для использования на низком уровне)...
ведь:
vector<OBJECT*> objects; // массив указателей в "обертке", для стабильности
... << RSDN@Home 1.1 beta 1 >>
Re[2]: Зачем
От:
Аноним
Дата:
08.08.03 06:53
Оценка:
_>Еще как! Особенно указатели на ссылки на двухмерные массивы указателей!
Извини, но это бред. Указателей на ссылку не бывает.
Re[3]: Зачем
От:
Аноним
Дата:
08.08.03 07:11
Оценка:
Здравствуйте, Аноним, Вы писали:
_>>Еще как! Особенно указатели на ссылки на двухмерные массивы указателей!
А>Извини, но это бред. Указателей на ссылку не бывает.
Здравствуйте, Аноним, Вы писали:
А>нужны указатели на указатели? А>В принципе?
В свете вашего вопроса, родился встречный:
Как, в принципе, вы собираетесь создавать двумерные массивы динамического размера ?
Приведем рекурсивное определение:
В С++ понятие массива и области памяти практически идентичны с той разницей, что работа с массивом конкретного размера и типа подразумевает под собой какую-то определенную адресную арифметику. Очевидно, что одномерный массив — это область памяти, причем имя массива(переменная) — это указатель на эту область (точнее, на первый елемент массива в памяти, равный по размеру sizeof(тип массива)). Думаю, что вы также согласитесь со мной, что двумерный массив — это массив одномерных массивов, поэтому мы с легкостью можем его рассматривать, как ОДНОМЕРНЫЙ массив указателей на первые элементы массивов-элементов. Понятно, что в таком случае имя двумерного массива — это указатель на его первый элемент, а для него это имя первого одномерного массива, т.е. указатель на первый элемент. Следовательно, переменная-имя двумерного массива — это указатель на указатель.
Т.е. пусть нам нужен массив переменной длины типа char (например, это массив строк, т.е. массив массивов символов)
char** ppcStrings; // указатель на указатель, не так ли?int iSomeValue1 = GetSomeValue (1);
int iSomeValue2 = GetSomeValue (2);
ppcStrings = new char [iSomeValue1][iSomeValue2];
В таком случае выражие **ppcStrings будет идентично выражению arStrings в следующем листинге:
Разница, правда, очевидна: массив переменной(динамической) длины возможен лишь только в первом случае, так как во втором случае используется ранее связывание и нет возможности определить длину массива во время выполнения.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Главное, быть до конца честными, и добавить, что с указателями в точности та же ситуация.
Если переменная объявлена сразу как указатель, то да. А если как обычная переменная, то при передачи указателя придется писать & перед ней, что уже наводит на мысль...
Здравствуйте, Jenyay, Вы писали:
J>не ясно, устанавливается параметр какой-то внутренний (если это метод класса) или n. Только по смыслу надо смотреть.
J>Вот у Голуба, наверное, лучше написано:
Правильно написано. Однако нет правил без исключений. Например смотри мой пост про swap.
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
WH>Правильно написано. Однако нет правил без исключений. Например смотри мой пост про swap.
Согласен. И еще, наверное, класс std::string проще передавать по ссылке (если ее надо изменить). По крайней мере так привычнее после char*, когда пишешь только имя переменной (там то уже подразумевается, что это указатель). Но я насчет этого не уверен и иногда все-равно передаю через указатель на string.
Здравствуйте, Jenyay, Вы писали:
ПК>> Главное, быть до конца честными, и добавить, что с указателями в ПК>> точности та же ситуация.
J> Если переменная объявлена сразу как указатель, то да.
Не только, с использованием результатов вызова других функций — то же самое.
J> А если как обычная переменная, то при передачи указателя придется писать & перед J> ней, что уже наводит на мысль...
А факт нерегулярности таких подсказок не наводит на мысль, что что-то не так с выбранным
стилем? Ведь все равно, при таком подходе, отсутствие & не может служить знаком, что
функция не изменяет то, что ей передают. Когда-то я тоже пробовал использовать такой стиль,
но отказался от него из-за указанной нерегулярности и того факта, что при таком подходе,
весь код замусоривается дополнительными разыменованиями и взятиями адреса.
Posted via RSDN NNTP Server 1.6 RC1
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
J>> А если как обычная переменная, то при передачи указателя придется писать & перед J>> ней, что уже наводит на мысль...
ПК>А факт нерегулярности таких подсказок не наводит на мысль, что что-то не так с выбранным ПК>стилем? Ведь все равно, при таком подходе, отсутствие & не может служить знаком, что ПК>функция не изменяет то, что ей передают. Когда-то я тоже пробовал использовать такой стиль, ПК>но отказался от него из-за указанной нерегулярности и того факта, что при таком подходе, ПК>весь код замусоривается дополнительными разыменованиями и взятиями адреса.
Вот поэтому я и стараюсь не использовать ссылок. А, впринципе, как тогда быть (всмысле регулярности)? Какой тогда стиль использовать?