|
|
  |
К знатокам, Локальные переменные. |
|
|
|
Sep 28 2007, 07:49
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(alexander55 @ Sep 28 2007, 07:53)  Конструкторы позволяют всю инициализацию убрать в классы. При этом получается следующие плюсы C++: -отсутствие бесчисленной инициализации в main; Все в этом хорошо кроме двух существенных недостатков - в конструкторах нельзя использовать сервисы ОС, ибо ос запускается из main() и второй - не гарантирован порядок вызова конструкторов. И если один объект использует при инициализации данные другого - возможны три варианта: 1) повезет и порядок случайно окажется нужным 2а) не повезет, и работать не будет, 2б) не повезет, но работать будет 3) завести в каждом классе init() и вызвать его в нужном порядке из main(), а конструктор не нужен. Как решать такие ситуации? Объясню на двух простых примерах: 1) scmRTOS. Каждый поток в своем конструкторе прописывает себя в таблице объекта Kernel. Чаще всего объект Kernel еще не существует и спасает лишь то, что он глобальный, и его адрес известен компилятору. Потоки складывают данные туда, где потом разместится Kernel. Это случай типа 2б и спасает лишь то, что конструктор TKernel фактически разбит на две части - сначала при обнулении неинициализированных глобальных переменных обнуляется и память на месте размещения Kernel, а потом, при конструировании объекта заполняются своими значениями ненулевые члены. Здесь процесс борьбы вроде бы очевиден - поскольку ядро может быть лишь одно, то его можно сделать виртуальным базовым классом для потоков или все его(ядра) члены - статическими. Второй способ менее элегантен, ибо придется где-то отдельно перечислять инициализацию каждого члена. 2) Микросхема Wiznet. Имеет 4 сокета (каждый со своим набором регистров) и набор общих настроек. Описана классом TWiznet, который имеет членами массив из четырех TSocket. Вроде все логично. Есть объекты (Ttelnet, Ttftp), которые используют сокеты и которые по хорошему в конструкторах должны делать настройку сокетов, но проблема в том, что на момент вызова конструкторов этих объектов объекты сокетов еще не созданы. Тупик, приводящий к варианту 3. Для Непомнящий Евгений: Посмотрел ваш исходник, есть вопрос. Почему вы в конструкторе используете конструкцию { this->member = initvalue; } вместо рекомендуемой Страуструпом (правда он тоже не всегда ее придерживается) : member1(initvalue1) {}?
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Sep 28 2007, 08:38
|

Adept
     
Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343

|
Цитата(Сергей Борщ @ Sep 28 2007, 14:49)  Как решать такие ситуации? Объясню на двух простых примерах: 1) scmRTOS. Каждый поток в своем конструкторе прописывает себя в таблице объекта Kernel. Чаще всего объект Kernel еще не существует и спасает лишь то, что он глобальный, и его адрес известен компилятору. Потоки складывают данные туда, где потом разместится Kernel. Это случай типа 2б и спасает лишь то, что конструктор TKernel фактически разбит на две части - сначала при обнулении неинициализированных глобальных переменных обнуляется и память на месте размещения Kernel, а потом, при конструировании объекта заполняются своими значениями ненулевые члены. Оно не то, чтобы спасает, оно на этом и основано - объект Kernel размещается статически, т.е. память под него выделяется на этапе компиляции, и на рантайме туда уже писать вполне безопасно. Безотносительно к тому, инициализирована эта память или нет. Цитата(Сергей Борщ @ Sep 28 2007, 14:49)  2) Микросхема Wiznet. Имеет 4 сокета (каждый со своим набором регистров) и набор общих настроек. Описана классом TWiznet, который имеет членами массив из четырех TSocket. Вроде все логично. Есть объекты (Ttelnet, Ttftp), которые используют сокеты и которые по хорошему в конструкторах должны делать настройку сокетов, но проблема в том, что на момент вызова конструкторов этих объектов объекты сокетов еще не созданы. Тупик, приводящий к варианту 3. Есть честный обходной путь. Размещай объявления объектов в одной единице компиляции и порядок вызова конструкторов будет гарантирован. На уровне проекта это вполне нормальное и устойчивое решение.
--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
|
|
|
|
|
Sep 28 2007, 09:44
|
Знающий
   
Группа: Свой
Сообщений: 771
Регистрация: 16-07-07
Из: Волгодонск
Пользователь №: 29 153

|
Цитата(Сергей Борщ @ Sep 28 2007, 11:49)  Для Непомнящий Евгений: Посмотрел ваш исходник, есть вопрос. Почему вы в конструкторе используете конструкцию { this->member = initvalue; } вместо рекомендуемой Страуструпом (правда он тоже не всегда ее придерживается) : member1(initvalue1) {}? У меня имя члена совпадает с именем аргумента, и я как-то не пробовал что получится при записи : member1(member1) {} Ну и как-то привычнее  . Я списки инициализации использую для конструкторов предков, объектов-членов и констант, а все остальное обычно размещаю в теле конструктора.
Сообщение отредактировал Непомнящий Евгений - Sep 28 2007, 09:48
|
|
|
|
|
Sep 28 2007, 10:30
|

Adept
     
Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343

|
Цитата(Непомнящий Евгений @ Sep 28 2007, 16:44)  У меня имя члена совпадает с именем аргумента, и я как-то не пробовал что получится при записи : member1(member1) {} Нормально должно все быть. Ситуация с именами не отличается от варианта, когда аргумент присваивается в теле конструктора, а не в списке инициализации. Цитата(Непомнящий Евгений @ Sep 28 2007, 16:44)  Ну и как-то привычнее  . Я списки инициализации использую для конструкторов предков, объектов-членов и констант, а все остальное обычно размещаю в теле конструктора. Дык для этого и введен универсальный синтаксис, чтобы единообразно все делать. Кроме того, как вы правильно заметили, проинициализировать константные члены другого способа и нет.
--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
|
|
|
|
|
Sep 28 2007, 20:09
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(Непомнящий Евгений @ Sep 28 2007, 12:44)  Ну и как-то привычнее  . Я списки инициализации использую для конструкторов предков, объектов-членов и констант, а все остальное обычно размещаю в теле конструктора. Но ведь тот же Страуструп пишет, что если член будет не POD-типа, то при таком варианте для члена сначала будет вызван конструктор по умолчанию, а потом (уже в теле) копирующий конструктор. К тому же если член не имеет конструктора по умолчанию, то компилятор просто выдаст ошибку. Правильно ли я понял, что все сводится только к эстетическим соображениям? Я пока только учусь, поэтому хочу разобраться
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Sep 28 2007, 21:29
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(dxp @ Sep 22 2007, 20:52)  Я не буду оригинальным, просто повторю, что говорят авторитетные дядьки вроде Г.Буча и Б.Страуструпа. Самое главное при объектном подходе - это сопоставлять классы и объекты объектам реального мира. Мир в восприятии человека представляется дискретным - человек выделяет в нем более-менее законченные целостные объекты, понятия, которыми он оперирует, строя модели. Так вот объектные (и объектно-ориентированные) языки позволяют на уровне средств языка оперировать типами, отображаемыми на объекты реального мира. Просто старайтесь вычленить в своей программе и прежде всего в той предметной области, которую описывает программа, эти самые законченные (на достаточном уровне абстракции, конечно) объекты и понятия, а после этого уже можно спокойно описывать эти объекты у себя в программе. Этот подход позволяет при разработке программы сразу думать на уровне объектов, а не разрозненных переменных, связи между которыми при отсутствии тех же классов разработчику приходится держать в голове. Понятно, что думать на уровне объектов реального мира намного проще, чем на уровне гораздо большего количества неких в разной мере абстрактных переменных встроенных типов. [quote name='dxp' post='299963' date='Sep 28 2007, 08:31'] Ну, собсно, тут на плюсах особенно не развернуться, чтобы показаться во всей красе  , Код ........................... class TPorts { public: INLINE void read() { pinb = PINB; // читаем порты pinc = PINC; pind = PIND; }
private: byte pinb; byte pinc; byte pind; } ports; [/quote]Не могли бы Вы на примере Вашего объекта ответить на несколько вопросов
Класс который вы назвали TPorts(хотя логичнее его было бы назвать TPortsBCD) это дискретный законченный целостный объект ? (про незаконченную реализацию я уже читал...) Если в другом проекте мне понадобятся порты С, D и E, я должен буду создавать для этого новый класс ? Если в другом проекте мне понадобится не 3 таких порта а например 2 или 4, я должен создать новый класс ? (с самого начала) Если иногда мне нужно читать пины всех портов одновременно, а иногда только пины одного порта, я должен в этом классе предусмотреть функции для чтения каждого порта по отдельности ? А если мне нужно еще и писать иногда в порт, причем в любой из 3 по определенным битам ? ......................................... и т.д. .......
Для меня Ваш класс как минимум нелогичен, ИМХО, начинать таки нужно с одного класса под названием TPort(один порт, любой). Ваш класс, ИМХО, имеет очень маленькое отношение к ОПП, это больше похоже на вариант "меня попросили написать с классами, я и написал".
P.S. А насчет того что развернуться негде, еще как есть... Тока оверхеда при этом будет мама не горюй...
[quote name='dxp' post='299963' date='Sep 28 2007, 08:31'] Кстати, о кодогенерации. Вот, что получилось: ......................... По-моему, тут и руками лучше не напишешь. :) [/quote]попробуйте вот так: [code] INLINE void handle() { byte tmp; adcl = ADCL; // читаем результат последнего преобразования adch = ADCH; ADMUX = admux; // новый канал tmp = adcsra; ADCSRA = tmp; // запускаем новое преобразование systickstart = tmp; } Должно слегка помочь  И это исчо без применения асм и всяких хаков
|
|
|
|
|
Sep 29 2007, 04:14
|
Знающий
   
Группа: Свой
Сообщений: 771
Регистрация: 16-07-07
Из: Волгодонск
Пользователь №: 29 153

|
Цитата(singlskv @ Sep 29 2007, 01:29)  Тока оверхеда при этом будет мама не горюй... Мой сослуживец писал класс "Ножка порта". Использовал шаблон и инлайн-функции. Проверил результат - оверхеда нету. Потом параметризовал такими классами класс, считающий импульсы. Единственное, что мне не нравится в таком подходе - если использовать шаблоны на низком уровне иерархии, то они "тянутся" наверх; притом реализацию функций класса-шаблона надо делать в h-файле. Цитата(Сергей Борщ @ Sep 29 2007, 00:09)  Но ведь тот же Страуструп пишет, что если член будет не POD-типа, то при таком варианте для члена сначала будет вызван конструктор по умолчанию, а потом (уже в теле) копирующий конструктор. К тому же если член не имеет конструктора по умолчанию, то компилятор просто выдаст ошибку.
Правильно ли я понял, что все сводится только к эстетическим соображениям? Я пока только учусь, поэтому хочу разобраться Так я ж вроде бы и сказал, что для инициализации объектов - членов, предков и констант использую список инициализации. Члены встроенных типов я инициализирую в теле конструктора по привычке; может стоит от нее отказаться. Счас проверил, запись вида : member1(member1) действительно работает правильно.
|
|
|
|
|
Sep 29 2007, 07:04
|

Adept
     
Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343

|
Цитата(singlskv @ Sep 29 2007, 04:29)  Класс который вы назвали TPorts(хотя логичнее его было бы назвать TPortsBCD) это дискретный законченный целостный объект ? (про незаконченную реализацию я уже читал...) Как может быть незаконченный объект быть "дискретным законченным целостным" объектом? Любой объект класса дискретный по определению. Насколько он целостный, зависит от многих факторов, но как правило - это субъективная характеристика. А то, что он незаконченный, я уже сказал и объяснил почему. Цитата(singlskv @ Sep 29 2007, 04:29)  Если в другом проекте мне понадобятся порты С, D и E, я должен буду создавать для этого новый класс ? Если в другом проекте мне понадобится не 3 таких порта а например 2 или 4, я должен создать новый класс ? (с самого начала) Если иногда мне нужно читать пины всех портов одновременно, а иногда только пины одного порта, я должен в этом классе предусмотреть функции для чтения каждого порта по отдельности ? А если мне нужно еще и писать иногда в порт, причем в любой из 3 по определенным битам ? ......................................... и т.д. ....... Это все вопросы уровня проектирования. Безусловно, я не могу написать реализацию, которая вас устроила бы, и никто не сможет, если он не телепат. Только вы сами можете это сделать. Я написал пример, основываясь на том коде, который вы привели. Что из него можно было понять? Только то, что в прерывании по системного таймеру производится чтение данных АЦП, запуск нового преобразования и чтение портов. Как между собой связаны таймер, АЦП и порты, я не знаю. Почему чтение и запуск АЦП производится в прерывании от таймера, а не в собственном, я тоже не знаю. Но не берусь давать такому подходу оценку, т.к. не знаю ни предметной предметной области, ни конкретной ситуации, для которой написана программа. Я лишь написал с помощью классов тот фрагмент, который был предложен к рассмотрению. Но ведь исходно вопрос-то возник не из-за этих вопросов проектирования, а из-за того, что якобы, классы привносят в код оверхед сами по себе. Это нисколько не так, что приведенный пример, надеюсь, ясно продемонстрировал.Цитата(singlskv @ Sep 29 2007, 04:29)  Для меня Ваш класс как минимум нелогичен, ИМХО, начинать таки нужно с одного класса под названием TPort(один порт, любой). Это и есть проектирование программы. Вам тут безусловно виднее. Но то, что об этом заходит речь - это показывает различия в подходе при разработке программы на С и на C++. На С можно писать код, не задумываясь об этих моментах - программных сущностях, их иерархиях и взаимосвязях, - многие так и пишут, "в лоб". И, как правило, такие программы не отличаются структурной стройностью, внутренней логичностью, тяжелее в расширении и сопровождении. Конечно, можно писать на С и по-другому, разработав структуру и придерживаясь ее, но это требует известных усилий и квалификации. С++ же напротив - тяготеет к такому стилю, при использовании классов сам подход сводится к определению нужных классов, наделения их необходимой функциональностью. А для этого надо сразу четко представлять, что мы имеем, что хотим реализовать, какие сущности у нас присутствуют в предметной области и т.д. Т.е. этап проектирования возникает сразу в явном виде на базе объектного подхода. Поскольку все эти различия идут на уровне исходного кода, кодогенерация и эффективность по сравнению с С не страдают. Цитата(singlskv @ Sep 29 2007, 04:29)  Ваш класс, ИМХО, имеет очень маленькое отношение к ОПП, Он вообще к ООПу никакого отношения не имеет. Цитата(singlskv @ Sep 29 2007, 04:29)  это больше похоже на вариант "меня попросили написать с классами, я и написал". Почти так. Меня попросили реализацию фрагмента на С++, я ее сделал. Вы не это просили разве? Цитата(singlskv @ Sep 29 2007, 04:29)  P.S. А насчет того что развернуться негде, еще как есть... Тока оверхеда при этом будет мама не горюй... Не понял, что вы тут имели в виду. И про какой оверхед речь? Цитата(singlskv @ Sep 29 2007, 04:29)  попробуйте вот так: Код INLINE void handle() { byte tmp; adcl = ADCL; // читаем результат последнего преобразования adch = ADCH; ADMUX = admux; // новый канал tmp = adcsra; ADCSRA = tmp; // запускаем новое преобразование systickstart = tmp; } Должно слегка помочь  И это исчо без применения асм и всяких хаков  За счет чего? И причем тут уже ++?
--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
|
|
|
|
|
Sep 29 2007, 19:59
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(dxp @ Sep 29 2007, 11:04)  Почему чтение и запуск АЦП производится в прерывании от таймера, а не в собственном, я тоже не знаю. Но не берусь давать такому подходу оценку, т.к. не знаю ни предметной предметной области, ни конкретной ситуации, для которой написана программа. Дык ответ то прост и банален, потому что джиттера не будет  при чтении портов например... Цитата Я лишь написал с помощью классов тот фрагмент, который был предложен к рассмотрению. Цитата Но ведь исходно вопрос-то возник не из-за этих вопросов проектирования, а из-за того, что якобы, классы привносят в код оверхед сами по себе. Это нисколько не так, что приведенный пример, надеюсь, ясно продемонстрировал. dxp, я надеюсь что Вы сами то понимаете что слегка лукавите ? Вариант классы ради классов я все-таки не рассматривал. Вы будете меня убеждать что класс подобный описанному Вами будет когда-либо присутствовать в Вашей программе ? (лучше соврите если это правда  ) Цитата С++ же напротив - тяготеет к такому стилю, при использовании классов сам подход сводится к определению нужных классов, наделения их необходимой функциональностью. А для этого надо сразу четко представлять, что мы имеем, что хотим реализовать, какие сущности у нас присутствуют в предметной области и т.д. И эта функциональность бывает иногда(часто) излишней для конкретного проекта. Заметим сразу же что если какая-то функция реализованна в классе, то линкер не имеет возможности выкинуть эту функцию из конечного бинарника(или имеет такое право..., засомневался, кто знает просветите...) Цитата Он вообще к ООПу никакого отношения не имеет. Почти так. Меня попросили реализацию фрагмента на С++, я ее сделал. Вы не это просили разве? Повторюсь еще раз, классы ради классов...  Цитата Не понял, что вы тут имели в виду. И про какой оверхед речь? Все очень просто, Вы создаете реальный(похожий на реальность) класс под названием типа TPort. Этот объект будет включать в себя переменную указывающую на адрес регистра PINx(в общем случае там таких переменных будет 3 (pin, port и ddr), хотя в частных реализациях можно обойтись и одним адресом). Так же в этом классе будет реализован доступ к этому порту в разных вариантах, типа считать, записать, считать с маской, записать с маской, и т.д. ну и конечно конструктор с привязкой к конкретному порту. Про оверхед нужно объяснять или сами все поняли ? Цитата И причем тут уже ++? С++ здесь уже действительно не при чем. Цитата За счет чего? Ну это вроде очивидно, за счет исключения лишнего считывания adcsra из памяти.
|
|
|
|
|
Sep 30 2007, 11:12
|

Adept
     
Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343

|
Цитата(singlskv @ Sep 30 2007, 02:59)  dxp, я надеюсь что Вы сами то понимаете что слегка лукавите ? Вариант классы ради классов я все-таки не рассматривал. Вы будете меня убеждать что класс подобный описанному Вами будет когда-либо присутствовать в Вашей программе ? (лучше соврите если это правда  ) Не понимаю, где я дал основание подозревать меня во вранье? Подобные мелкие классы я использовал и использую в своих программах, т.к. не вижу тому препятствий. Удобство же налицо - логическое объединение кода в отдельное пространство имен с возможностью управления доступом. Если вам это не надо, это не причина для обвинений во лжи. Цитата(singlskv @ Sep 30 2007, 02:59)  И эта функциональность бывает иногда(часто) излишней для конкретного проекта. Заметим сразу же что если какая-то функция реализованна в классе, то линкер не имеет возможности выкинуть эту функцию из конечного бинарника(или имеет такое право..., засомневался, кто знает просветите...) А почему не имеет? Что мешает? Функция-член класса ничем особенным в бинарнике не отличается. Вот если это виртуальная функция, то ее выкинуть нельзя, т.к. ее адрес помещается в vtbl. Но и на С если есть массив указателей на функции, то все функции будут включены, даже если какие-то из них не используются. Цитата(singlskv @ Sep 30 2007, 02:59)  Повторюсь еще раз, классы ради классов...  Классы ради удобства логического представления. Цитата(singlskv @ Sep 30 2007, 02:59)  Все очень просто, Вы создаете реальный(похожий на реальность) класс под названием типа TPort. Этот объект будет включать в себя переменную указывающую на адрес регистра PINx(в общем случае там таких переменных будет 3 (pin, port и ddr), хотя в частных реализациях можно обойтись и одним адресом). Так же в этом классе будет реализован доступ к этому порту в разных вариантах, типа считать, записать, считать с маской, записать с маской, и т.д. ну и конечно конструктор с привязкой к конкретному порту. Вы мне пеняете, что в моих примерах классы ради классов, и тот час же прелагаете написать еще пример, где будет класс только ради класса. Для начала надо уяснить, какова цель написания такого кода. Она мне не ясна. Тем не менее, я готов написать еще раз пример при условии, что вы напишете сначала ровно такой же по функциональности пример на С. Цитата(singlskv @ Sep 30 2007, 02:59)  Про оверхед нужно объяснять или сами все поняли ? См выше. Кроме того, оверхед зависит от целевого процессора. Например, на MSP430 оверхеда не будет. Цитата(singlskv @ Sep 30 2007, 02:59)  Ну это вроде очивидно, за счет исключения лишнего считывания adcsra из памяти. Если вы заметили, у меня там представление класса объявлено без квалификаторов volatile, поэтому компилятору ничего не мешает оптимизировать доступ и не читать два раза. То, что он этого не сделал - это вопрос к качеству кодогенерации данного компилятора. А использовать временную переменную - это заниматься низкоуровневой оптимизацией на высоком уровне, что попахивает опять же хаком, хотя и безобидным в данном случае (из минусов то, что на пустом месте загромождается код). По правильному бороться с этим недостатком компилятора надо путем написания репорта в саппорт, чтобы этот момент в будущих версиях пофиксили. Возвращаясь к основной теме. Мне не понятно, какую вы преследуете цель в рамках данной дискуссии. Если хотите понять преимущества С++, то стоит уже перестать спорить с очевидным и начать пробовать писать - "дров" по С++ вам тут накидали достаточно. Там появятся совсем другие вопросы, на которые участники темы, использующие С++ в своей работе (включая и меня), думаю, с удовольствием ответят. Если же вы пытаетесь мне доказать, что С++ не нужен и достаточно С, то это пустая потеря времени с вашей стороны - я достаточно знаком с обоими языками и в теории, и на практике, чтобы иметь собственное мнение и глубокую убежденность, основанную на собственном опыте. Поэтому, дабы прекратить переливание из пустого в порожнее, наверное, буду сворачивать свое участие в теме в этом контексте.
--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
|
|
|
|
|
Sep 30 2007, 19:35
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Цитата(dxp @ Sep 30 2007, 14:12)  Поэтому, дабы прекратить переливание из пустого в порожнее, наверное, буду сворачивать свое участие в теме в этом контексте. Тем не менее спасибо за полезную информацию. Несмотря на то, что получилось что-то вроде спора людей с разными взглядами, надо учесть то что некоторые не вступали в дискуссию, так как не имели знаний и опыта применения. Но при этом читали её и пытались делать свои выводы. С этой точки зрения, как мне кажется, дискуссия была очень полезна.
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|