Невживані параметри







Питання Мені попадався C + +-код, Де для невикористовуваних параметрів застосовується UNREFERENCED_PARAMETER, наприклад:

int SomeFunction(int arg1, int arg2){ UNREFERENCED_PARAMETER(arg2) …}

Але зустрічався і такий код:

int SomeFunction(int arg1, int /* arg2 */){ …}

Не могли б ви пояснити, в чому тут різниця і що краще?

Джуді Макгео (Judy McGeough)

Відповідь Ну звичайно. Почнемо з UNREFERENCED_PARAMETER. Це макрос, визначений в winnt.h:

#define UNREFERENCED_PARAMETER(P) (P)

Інакше кажучи, UNREFERENCED_PARAMETER розкривається в передаваний параметр або вираз. Його призначення – уникнути попереджень компілятора про наявність параметрів, на які немає посилань (невикористовуваних параметрів). Багато програмістів, включаючи вашого вірного слугу, воліють компілювати при найвищому рівні попереджень, Level 4 (/ W4). Попередження Level 4 потрапляють в категорію того, що можна спокійно ігнорувати. Невеликі погрішності не зашкодять вашому коду, але можуть створити про вас погане враження. Наприклад, ви написали десь у своїй програмі рядок на зразок:

int x=1;

Однак ви ніде не використовуєте x. Можливо, цей рядок залишилася з тих пір, коли ви дійсно використовували x, але потім видалили частину коду, а про цю змінної забули. Попередження Level 4 допомагають виявляти такі дрібні промахи. Так чому б не дозволити компілятору допомогти вам у досягненні вис-дової рівня професіоналізму? Успішна компіляція з попередженнями Level 4 дозволяє пишатися своєю роботою. Проблема в тому, що при Level 4 компілятор скаржиться на зовсім безневинні речі, наприклад на невживані параметри (звичайно, вони нешкідливі, тільки якщо ви дійсно ними не користуєтесь). Припустимо, у вас є функція з двома аргументами, але ви використовуєте лише один з них:

int SomeFunction(int arg1, int arg2)
{
return arg1+5;
}

При / W4 компілятор повідомить: “warning C4100:” arg2 “: unreferenced formal parameter”. Щоб обдурити компілятор, можна додати UNREFERENCED_PARAMETER (arg2). Тепер ваша функція посилається на arg2, і компілятор заткнеться. А оскільки вираз:

arg2;

нічого не робить, компілятор не буде генерувати для нього ніякого коду, тому ви не програєте ні в розмірі, ні в ефективності.

Гострі уми можуть поцікавитися: якщо arg2 не використовується, то навіщо взагалі оголошувати його? Зазвичай так роблять, коли реалізують функцію, яка повинна відповідати певній сигнатурі API, спущеної згори. Наприклад, у MFC-обробника OnSize повинна бути наступна сигнатура:

void OnSize(UINT nType, int cx, int cy);

Тут cx і cy – нові ширина і висота вікна, а nType – якийсь код зразок SIZE_MAXIMIZED, якщо вікно повинно бути повністю розгорнуто, або SIZE_RESTORED при нормальному розмірі. Як правило, nType вас не хвилює – ви піклуєтеся тільки про cx і cy. Тому при компіляції з ключем / W4 вам знадобиться UNREFERENCED_PARAMETER (nType). А адже OnSize – лише одна з тисяч функцій в MFC і Windows. Так що досить важко написати Windows-програму без параметрів, на які немає посилань.

Ну і вистачить про UNREFERENCED_PARAMETER. Як Джуді помітила, ставлячи своє запитання, ще один часто застосовуваний C + +-програмістами трюк, який дозволяє добитися того ж результату, – висновок зайвого для них параметра в сигнатурі функції в ознаки коментаря:

void CMyWnd::OnSize(UINT /* nType */,
int cx, int cy)
{
}

Тепер nType є безіменним параметром; те ж саме ви отримали б, набравши OnSize (UINT, int cx, int cy). А зараз питання за 64 000 доларів: яким способом ви б скористалися – безіменним параметром або UNREFERENCED_PARAMETER?

Зазвичай це не має ніякого значення; вибір визначається стилем. (Ви любите каву чорний або з вершками?) Але я можу придумати щонайменше одну ситуацію, де потрібен саме UNREFERENCED_PARAMETER. Припустимо, ви вирішили заборонити розгортання вашого вікна на весь робочий стіл. Ви відключаєте кнопку Maximize, видаляєте команду Maximize з системного меню і блокуєте будь-які інші елементи управління, які дозволили б повністю розгорнути вікно. Оскільки ви параноїк (а таке більшість хороших програмістів), ви додаєте вираз ASSERT, щоб бути впевненим у тому, що ваш код працює так, як було задумано:

void CMyWnd::OnSize(UINT nType, int cx, int cy)
{
ASSERT(nType != SIZE_MAXIMIZE);
… / / Використовуємо cx, cy
}

Команда тестувальників проганяє вашу програму 87 способами, ASSERT жодного разу не спрацьовує, і ви вирішуєте, що можна спокійно компілювати остаточну версію. Але без _DEBUG вираз ASSERT (nType! = SIZE_MAXIMIZE) розкривається в ((void) 0), і несподівано nType стає невживаним параметром! Ось вам і остаточна компіляція. Ви не можете закоментувати nType зі списку параметрів, тому що він потрібний для ASSERT. Тому в такій ситуації, де параметр використовується тільки в ASSERT або іншому _DEBUG-коді, лише UNREFERENCED_PARAMETER зробить компілятор щасливим як при налагоджувальної збірці, так і при остаточній. Вловили суть?

Перш ніж закруглитися, не можу не згадати, що індивідуальні попередження компілятора можна пригнічувати директивою pragma warning:

#pragma warning( disable : 4100 )

4100 – код помилки для невикористаного параметра. Ця директива діє на частину файлу або модуля. Щоб повторно включити дане преду-прежденіе:

#pragma warning( default : 4100 )

Однак краще зберегти стану всіх передбачених прежденій до відключення будь-якого з них, а потім, зробивши те, що вам потрібно, повернутися до колишньої конфігурації. Тоді ви зможете відновлювати свої колишні налаштування, а не конфігурацію компілятора за замовчуванням.

Ви могли б придушити попередження про невикористовувані параметрах в рамках однієї функції, оточивши її директивами pragma warning:

#pragma warning( push )
#pragma warning( disable : 4100 )
void SomeFunction(…)
{
}
#pragma warning( pop )

Звичайно, це було б занадто нудно для невикористовуваних параметрів, але, ймовірно, знадобиться для попереджень інших видів. Розробники бібліотек постійно використовують # pragma warning для блокування попереджень, щоб їх код можна було без проблем компілювати з ключем / W4. У MFC повно таких pragma. Параметрів у директив # pragma warning набагато більше, ніж я згадав тут. Перевірте їх в документації.

Питання Я помітив, що в деяких додатках є спеціальні команди, які з’являються в контекстному меню для кнопки таких додатків на панелі завдань. Наприклад, WinAmp (популярний медіа програвач) додає підменю “WinAmp” з командами, специфічними для WinAmp. Як мені додати власні команди для кнопки застосування на панелі завдань?

Жирар Осігян (Jirair Osygian)

Питання Я створила просте MFC SDI-додаток з формою, на якій відображається лічильник. Мені потрібно запускати і зупиняти лічильник клацанням правої кнопки миші на значку додатку, згорнутого в кнопку на панелі задач. Функції запуску і зупинки чудово працюють як кнопки на моїй формі, і мені вдалося додати аналогічні команди в системне меню. Але коли я вибираю їх у системному меню, нічого не відбувається. Як обробляти повідомлення від модифікованого системного меню?

Монік Шарман (Monicque Sharman)

Відповідь Я відповім на обидва питання одним махом. Відповідь на питання Жирара простий: меню, яке бачить користувач, клацнувши правою кнопкою миші згорнуте в кнопку на панелі завдань додаток, ідентично меню, отображаемому при клацанні значка додатка в лівому верхньому куті рядка заголовка або при натисненні Alt + Space. На рис. 1 показано, що я маю на увазі. Це меню називається системним, і в ньому є команди на кшталт Restore, Minimize, Maximize і Close.

Рис. 1. Системне меню

Викличте :: GetSystemMenu, щоб отримати системне меню, а потім змінюйте його, додавши, видаливши чи змінивши потрібні елементи. Ви можете або змінивши потрібні елементи. Ви можете навіть повністю блокувати системне меню, відключивши стиль WS_SYSMENU в прапорах створення свого вікна або у віртуальній функції PreCreateWindow. Але що б ви не робили, системне меню – це і те меню, яке відображається при щелч-ке правою кнопкою миші згорнутого в кнопку застосування на панелі завдань.

А це підводить нас до питання Монік: якщо в системне меню додаються власні команди, як їх обробляти в MFC? Якщо ви поступите як зазвичай – напишете десь обробник ON_COMMAND і додасте його в одну з своїх карт повідомлень (message maps), – то виявите, що цей обробник ніколи не викликається. В чому справа?

А справа в тому, що Windows і MFC обробляють системні команди інакше, ніж команди звичайного меню. Коли користувач клацає команду звичайного меню або кнопку на формі, Windows посилає вашого основного вікна повідомлення WM_COMMAND. Якщо ви користуєтеся MFC, її механізм диспетчеризації команд перехоплює це повідомлення і направляє його через систему будь-якого об’єкта в карті команд, у якого є обробник ON_COMMAND для цієї команди. (Докладніше про диспетчеризації команд в MFC див. мою статтю “Meandering Through the Maze of MFC Message and Command Routing” в “MSJ” за липень 1995 р. по посиланню www.microsoft.com/msj/0795/dilascia/dilascia.aspx. )

Але системні команди не йдуть через WM_COMMAND. Вони надходять через інше повідомлення, яке називається – а як же ще? – WM_SYSCOMMAND. Це відноситься як до істинно системним командам типу SC_MINIMIZE або SC_CLOSE, так і до додаються вами. Для обслуговування команд системного меню ви повинні явно обробляти WM_SYSCOMMAND і виконувати перевірку на ідентифікатори своїх команд. Це вимагає додавання ON_WM_SYSCOMMAND в карту повідомлень основного вікна, причому функція-обробник має виглядати приблизно так:

CMainFrame::OnSysCommand(UINT nID, LPARAM lp)
{
if (nID==ID_MY_COMMAND) {
… / / Обробляємо
return 0;
}
/ / Передаємо базового класу – це важливо!
return CFrameWnd::OnSysCommand(nID, lp);
}

Якщо команда не ваша, не забудьте передати її базового класу, – зазвичай це CFrameWnd або CMDIFrameWnd. Інакше Windows не отримає повідомлення і ви порушите роботу вбудованих команд.

Обробка WM_SYSCOMMAND в основний рамці діє нормально, але якась вона незграбна. Навіщо використовувати спеціальний механізм для обробки команд тільки тому, що вони приходять через системне меню? А що якщо ви захочете обробляти системну команду в якомусь іншому об’єкті, наприклад в документі? Одна поширена команда, що вміщується в системне меню, – About (ID_APP_ABOUT), і більшість MFC-програм обробляють ID_APP_ABOUT в об’єкті “додаток”:

void CMyApp::OnAppAbout()
{
static CAboutDialog dlg;
dlg.DoModal();
}

Одне з по-справжньому чудових засобів MFC – її система диспетчеризації команд (command-routing system), яка дозволяє об’єктам, відмінним від вікон, наприклад CMyApp, обробляти команди меню. Багато програмісти навіть не замислюються, наскільки це незвично. Якщо ви вже обробляєте ID_APP_ABOUT в своєму об’єкті “додаток”, то навіщо вам реалізувати окремий механізм для підтримки ID_APP_ABOUT в системному меню?

Більш ефективний спосіб (і в більшій мірі відповідає стилю MFC) обробки додаткових системних команд – передача їх через звичайний механізм диспетчеризації команд. Тоді ви змогли б обробляти системні команди стандартним для MFC чином – за допомогою обробників ON_COMMAND. Ви навіть змогли б використовувати ON_UPDATE_COMMAND_UI для поновлення елементів свого системного меню, наприклад для відключення якогось елементу або відображення галочки поруч з елементом.

На рис. 2 показаний написаний мною невеликий клас CSysCmdRouter, який перетворює системні команди в звичайні. Для його використання ви повинні створити екземпляр CSysCmdRouter в своїй основній рамці і викликати його метод Init з OnCreate:

int CMainFrame::OnCreate(…)
{
/ / Додаємо елементи в системне меню
CMenu* pMenu = GetSystemMenu(FALSE);
pMenu>AppendMenu(..ID_MYCMD1..);
pMenu>AppendMenu(..ID_MYCMD2..);
/ / Направляємо системні команди через MFC
m_sysCmdHook.Init(this);
return 0;
}

Викликавши CSysCmdRouter :: Init, ви можете обробляти ID_MYCMD1 і ID_MYCMD2 стандартним способом, за допомогою обробників ON_COMMAND для будь-якого об’єкта в MFC-схемі розподілу команд – уявлення (view), документа, рамки (frame), додатки або інший мішені, доданої перевизначенням OnCmdMsg. CSysCmdRouter також дозволяє оновлювати системне меню, використовуючи обробники ON_UPDATE_COMMAND_UI. Єдиний каверза – ідентифікатори ваших команд не повинні конфліктувати з ідентифікаторами будь-яких інших команд меню (якщо тільки вони не представляють ту ж команду) або вбудованих системних команд, які починаються з SC_SIZE = 0xF000. Visual Studio. NET присвоює ідентифікатори команд, починаючи з 0x8000 = 32768, тому що, якщо ви дозволите Visual Studio самостійно призначати ідентифікатори, то все буде в порядку за умови, що у вас не більше 0xF000-0x8000 = 0x7000 команд. У десятковому численні це 28 672 команди. Ну а якщо у вашому додатку понад 28 000 команд, вам потрібно сходити на консультацію до психіатра в області програмування.

Як працює CSysCmdRouter? Просто: він використовує мій універсальний клас CSubclassWnd, який я описував у багатьох колонках. CSubclassWnd дозволяє створювати підкласи віконних об’єктів MFC, не наслідуючи від них. CSysCmdRouter успадковує від CSubclassWnd і з його допомогою створює підклас основний рамки. Він перехоплює повідомлення WM_SYSCOMMAND, що посилаються рамці. Якщо ідентифікатор команди вкладається в діапазон, виділений системним командам (більше, ніж SC_SIZE = 0xF000), CSysCmdRouter пересилає його в Windows; в іншому випадку він ковтає WM_SYSCOMMAND і замінює його на WM_COMMAND, після чого MFC слід звичайним процедурам, викликаючи ваші обробники ON_COMMAND. Спритно, так?

А як щодо обробників ON_UPDATE_COMMAND_UI? Як CSysCmdRouter змушує їх спрацьовувати для команд системного меню? Елементарно. Перед виведенням меню Windows посилає вашого основного вікна повідомлення WM_INITMENUPOPUP. Це ваш шанс оновити елементи меню – включити або відключити їх, додати галочки і т. д. MFC захоплює WM_INITMENUPOPUP в CFrameWnd :: OnInitMenuPopup і виконує всі операції, пов’язані з оновленням UI. MFC створює об’єкт CCmdUI для кожного елемента меню і передає його відповідним обробникам ON_UPDATE_COMMAND_UI в ваших картах повідомлень. MFC-функція, що відповідає за цю роботу, – CFrameWnd :: OnInitMenuPopup, яка починається так:

void CFrameWnd::OnInitMenuPopup(CMenu* pMenu,
UINT nIndex, BOOL bSysMenu)
{
    if (bSysMenu)
return; / / не підтримує системне меню
     …
}

MFC нічого не робить для ініціалізації системного меню. Це бере на себе CSysCmdRouter. Він перехоплює WM_INITMENUPOPUP і скидає прапор bSysMenu, який є старшим словом в LPARAM:

if (msg==WM_INITMENUPOPUP) {
lp = LOWORD (lp); / / (присвоює HIWORD = 0)
}

Тепер, коли MFC отримує WM_INITMENUPOPUP, вона вважає, що меню звичайне. Все це прекрасно ралів CWnd :: WindowProc, або порівнювати HMENU, якщо вам ботает, поки ідентифікатори ваших команд не конфліктують з справжніми системними командами. Єдине, що ви втрачаєте, перевизначаючи OnInitMenuPopup, – можливість відрізняти системне меню від меню в основному вікні. Але не можна ж отримати все і відразу! Ви завжди можете обробляти WM_INIT-MENUPOPUP, переопреденужно розрізняти системне і звичайне меню. Але насправді вас не повинно хвилювати, звідки надходить команда.

Щоб показати, як все це діє на практиці, я написав тестову програму TBMenu. На рис. 3 представлено меню, виведене “правим клацанням” кнопки програми TBMenu на панелі завдань. Як бачите, в нижній частині меню є дві додаткові команди. Вихідний код CMainFrame в TBMenu наведено на рис. 4. У OnCreate додаються команди, які обслуговуються обробниками ON_COMMAND і ON_UPDATE_COMMAND_UI в карті повідомлень CMainFrame. TBMenu обробляє ID_APP_ABOUT усередині свого класу “додаток” (не показаний). CSysCmdRouter перетворює системні команди в звичайні.

 

Удачі в програмуванні!

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*