关于将形参P进行#define UNREFERENCED_PARAMETER(P) (P)未引用参数声明宏的用意

翻译自MSDN杂志2005年五月"C++ At Work"
添加链接描述
在这里插入图片描述
Q:典型如使用VS2019建立Windows桌面应用程序后向导自动生成的wWinMain()入口函数:

#include "framework.h"
#include "Project1.h"

#define MAX_LOADSTRING 100

// 全局变量:
HINSTANCE hInst;                                // 当前实例
WCHAR szTitle[MAX_LOADSTRING];                  // 标题栏文本 16位双字节字符
WCHAR szWindowClass[MAX_LOADSTRING];            // 主窗口类名

// 此代码模块中包含的函数的前向声明:
ATOM                MyRegisterClass(HINSTANCE hInstance); //返回unsigned short 2字节整数
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM); //返回long 4字节整数 作地址
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM); //返回int 4字节整数 作地址

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,         /*typedef void *HINSTANCE;句柄是一个二级指针*/
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,          /*wchar_t *LPWSTR 指向16位UNICODE宽字符的指针*/
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance); //传入的hPrevInstance本函数中不用 为了避免报"未引用形参"的警告 这里(hPrevInstance);进行一下无意义的引用 下同
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: 在此处放置代码。

    // 初始化全局字符串
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); //获取传入应用程序实例的应用程序标题栏文本到定义的全局宽字符串szTitle
    LoadStringW(hInstance, IDC_PROJECT1, szWindowClass, MAX_LOADSTRING);//获取传入应用程序实例的窗口类名到定义的全局宽字符串szWindowClass
    MyRegisterClass(hInstance);

    // 执行应用程序初始化:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_PROJECT1));

    MSG msg;

    // 主消息循环:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

Q+:如果函数内不使用形参那就直接注释掉好了,自然也不会出现“未引用形参“的警告啦:

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,         /*typedef void *HINSTANCE;句柄是一个二级指针*/
                     _In_opt_ HINSTANCE  /*hPrevInstance*/,
                     _In_ LPWSTR    /*lpCmdLine*/,          /*wchar_t *LPWSTR 指向16位UNICODE宽字符的指针*/
                     _In_ int       nCmdShow)
{
    
}

作者Judy McGeough答道:为什么呢?让我们从UNREFERENCED_PARAMETER开始,该宏在winnt.h中定义:

#define UNREFERENCED_PARAMETER(P) (P) //如此 UNREFERENCED_PARAMETER(p);相当于p;

换句话说,UNREFERENCED_PARAMETER()宏扩展到传入的参数或表达式。其目的是避免编译器警告未引用的参数。很多开发者,包括你自己,都喜欢以最高警告级别4级(/W4)进行编译。4级警告属于"可被安全忽略的事件"的一类,这些小的不幸(little infelicities)不会使你的代码崩溃(break your code),尽管它们可能让你不开心(make you look bad)。例如,你的代码中可能会有一些行像这样:

int x=1;

但你从不使用x。可能这一行是从你之前确实使用过x那会遗留下来的,但当你移除代码是忘记移除这个变量了。4级警告会发现这些小祸害(minor mishaps)。因此何不让编译器尽量帮助你达到最高级别的专业性(the highest level of professionalism)呢?使用4级警告进行编译时展示你对自己工作的自豪(show pride in your work)的一种方式。如果你正在为公共产品写一个库,那么4级警告是必须要达到的。你不会想强迫你的开发者干净利落地使用更低级别警告去编译他们的代码(force your developers to use a lower level to compile their code cleanly)。
问题在于,4级警告标准实在很挑剔。在4级警告中,编译者抱怨类似这些未引用参数的无害的事件(除非,当然,如果你的确要使用这些参数,那样的话你再不对这些参数引用的话就不是无害了)。也就是说你有一个函数带两个参数,但你只用一个:

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

当使用4级警告时,编译器会抱怨“warning C4100:‘arg2’:unreferenced formal parameter”(“警告 C4100:‘arg2’:未引用形参”)。为了糊弄编译器(fool the compiler),你可以增加UNREFERENCED_PARAMETER(arg2)。现在你的函数引用了arg2因此编译器会闭嘴(shut up)。并且因为表述:

arg2;

事实上啥也没干,编译器不会为之生成任何代码,因此既没有损失内存也不会损失效率(no loss of either space or efficiency)。
心细的可能会想:既然不使用形参arg2,为什么要在形参列表(函数域开始位置)声明它呢?这通常是因为你要实现一个功能以满足一些从天而降的API签名(implementating a function to meet some API signature ordained from the heavens)。例如,MFC的OnSize()处理函数(handler)必须具有以下签名:

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

这里的cx/cy是窗口的新的width/height nType是一个形如SIZE_MAXIMIZED的码当窗口被最大化 或者SIZE_RESTORED当正常化大小(normal sizing)。通常你不必关心nType;你只关心cx和cy。因此如果你想使用4级警告编译的话就需要用UNREFERENCED_PARAMETER(nType)。OnSize()只是数千个MFC和Windows函数中的一个。不使用未引用参数去写基于Windows的程序可能会非常艰难(hardly possible to write a Windows-based program without unreferenced parameters)。
UNREFERENCED_PARAMETER非常重要(so much for UNREFERENCED_PARAMETER)。正如在问题中说明的,C++程序员经常使用的能实现相同结果(避免未引用形参)的另一个技巧是将形参名从函数签名中注释掉:

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

现在nType是一个没有名字的参数,形同输入
OnSize(UINT,int cx,int cy);
因此现在的问题是:你该使用哪个方法——未命名参数 还是 UNREFERENCED_PARAMETER?
大多数情况下二者没区别,这纯粹是一种风格(purely a style thing)。(你喜欢你的java是黑色还是带奶油色?)但我能想出至少一种情景下需要使用UNREFERENCED_PARAMETER。假设你已经决定不允许你的窗口最大化。你使最大化按钮失效,从系统菜单移除最大化,并且阻止用户可以最大化的其他位置(block every other place the user could maximize)。因为你很偏执(paranoid,并且大多数优秀程序员都是偏执的),你加了一个断言来确保你的代码像你期望那样工作(working as you intended):

void CMyWnd::OnSize(UINT nType, int cx, int cy) { ASSERT(nType != SIZE_MAXIMIZE); ... // use cx, cy }

The QA team runs your program 87 ways and the ASSERT never bombs, so you figure it’s safe to compile a Release build. But now without _DEBUG defined, ASSERT(nType != SIZE_MAXIMIZE) expands to ((void)0) and suddenly nType becomes an unreferenced parameter! There goes your clean compile. You can’t comment nType out of the parameter list because you need it for the ASSERT. So in this situation—where the only place you use a parameter is within an ASSERT or other _DEBUG-conditional code—only UNREFERENCED_PARAMETER will keep the compiler happy in both Debug and Release builds. Got it?

Before closing, I’d be remiss not to mention that you can suppress individual compiler warnings using a pragma like so:

#pragma warning( disable : 4100 )

4100 is the error code for unreferenced parameter. The pragma suppresses the warning for the rest of the file/module. You can reenable the warning like so:

#pragma warning( default : 4100 )

However, a better approach is to store all of the warning states before disabling the specific warning, and then go back to that configuration when you’re done. That way, you get back to the state you were at previously and not just the compiler default.

So you could suppress unreferenced parameter warnings for a single function by surrounding it with pragmas like this:

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

Of course, that’s way too verbose for unreferenced parameters, but possibly necessary for other kinds of warnings. Library builders use #pragma warning all the time to block warnings so their code can compile cleanly with /W4. MFC is full of such pragmas. There’re even more #pragma warning options I haven’t mentioned. Check 'em out in the documentation.

/**********************************************************/
问者Jirair Osygian:
Q I’ve created a simple MFC single document interface (SDI) application with a form view to display a counter. I want to be able to start and stop the counter by right-clicking on the minimized application down on the task bar. The start and stop functions work fine as buttons on my form, and I was able to add the start/stop commands to the system menu. But when I click on them in the system menu, nothing happens. How can I handle these messages from my modified system menu?

Q I’ve created a simple MFC single document interface (SDI) application with a form view to display a counter. I want to be able to start and stop the counter by right-clicking on the minimized application down on the task bar. The start and stop functions work fine as buttons on my form, and I was able to add the start/stop commands to the system menu. But when I click on them in the system menu, nothing happens. How can I handle these messages from my modified system menu?
答者Monicque Sharman:
A I’ll answer both questions in one fell swoop. The answer to Jirair’s question is simple: the menu the user sees when you right-click on an application’s minimized task bar button is the same as the menu displayed if she clicks on the application icon in the top-left corner of the caption bar or presses Alt+Space. Figure 1 shows what I mean. This menu is called the system menu and has commands like Restore, Minimize, Maximize, and Close.

A I’ll answer both questions in one fell swoop. The answer to Jirair’s question is simple: the menu the user sees when you right-click on an application’s minimized task bar button is the same as the menu displayed if she clicks on the application icon in the top-left corner of the caption bar or presses Alt+Space. Figure 1 shows what I mean. This menu is called the system menu and has commands like Restore, Minimize, Maximize, and Close.
Sys Menu
Sys Menu

You can call ::GetSystemMenu to get the system menu, then modify it by adding, deleting, or changing items. You can even suppress the system menu entirely by turning off the WS_SYSMENU style in your window creation flags or virtual PreCreateWindow function. But whatever you do, the system menu is also the one that’s displayed when users right-click on your application’s minimized button in the task bar.Origins of Ctrl+Alt+Del

In my January column, I asked if anyone knew the origins of Ctrl+Alt+Del. Apparently several readers know how to use Google, because they sent me links to the same USA Today article I discovered before posing my challenge (see Thank this guy for ‘control-alt-delete’). Ctrl+Alt+Del was invented by a man named David J. Bradley who worked at IBM.

IBM decided it would be good to have a way to reset their new PC without turning off the power. And why those particular keys, Ctrl+Alt+Del? For technical reasons, David needed to use two modifier keys. He also wanted a combination no one was likely to type by accident. So he chose Ctrl+Alt as the modifier keys (less common than Shift) and Delete, which is on the other side of the keyboard, so typing Ctrl+Alt+Del required two hands. At least it did in the old days.

Modern keyboards now have Ctrl and Alt on the right side as well. The reset feature was originally intended to be a secret escape hatch for IBMers, but inevitably the cat got out of the bag. Once developers learned about it, they started telling customers to use it as a last resort when their machines were hung. The rest is history. Ctrl+Alt+Del is affectionately known as the “three finger salute,” and survives in Windows even today, where it invokes the Task Manager so you can kill hung tasks or shut your system down (you can learn more about Secure Attention Sequences like Ctrl+Alt+Del in this month’s Security Briefs column). And what if Ctrl+Alt+Del fails? Why, just hold the power button down for five seconds, please.

David Bradley was one of the original 12 engineers who built the IBM Personal Computer. He wrote the ROM BIOS. For a brief bio on David, see David J. Bradley.

Which leads to Monicque’s question: if you add your own commands to the system menu, how do you handle them in MFC? If you do the normal thing—write an ON_COMMAND handler somewhere and add it to one of your message maps, you’ll discover your handler never gets called. How come?

It’s because Windows and MFC handle system commands differently from ordinary menu commands. When the user invokes a normal menu command or button in your form, Windows sends your main window a WM_COMMAND message. If you’re using MFC, MFC’s command-routing mechanism catches this message and routes it through the system to any object in the command route that has an ON_COMMAND handler for that command. (For details on MFC command routing, see my article “Meandering Through the Maze of MFC Message and Command Routing” in the July 1995 MSJ.)

But system commands don’t come via WM_COMMAND. They come via a different message called—what else?—WM_SYSCOMMAND. This is true whether the command ID is one of the true system commands like SC_MINIMIZE and SC_CLOSE, or some other command ID you’ve added. To handle system menu commands, you have to handle WM_SYSCOMMAND explicitly and check for your own command IDs. This requires adding ON_WM_SYSCOMMAND to your main window’s message map, with a handler function like this:

CMainFrame::OnSysCommand(UINT nID, LPARAM lp) { 
    if (nID==ID_MY_COMMAND) { ... 
    // handle it return 0; 
    } 
    // pass to base class: important!    return CFrameWnd::OnSysCommand(nID, lp); 
}

If the command isn’t yours, don’t forget to pass it to your base class—which is typically CFrameWnd or CMDIFrameWnd. Otherwise, Windows won’t get the message and you’ll break the built-in commands.

Handling WM_SYSCOMMAND in your main frame certainly works, but it feels kludgy. Why use a special mechanism to handle commands just because they come via the system menu? What if you want to handle a system command in some other object like your view or document? One common command to put in your system menu is About (ID_APP_ABOUT), and most MFC programs handle ID_APP_ABOUT in the application object:

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

One of the really cool features of MFC is its command-routing system, which lets non-window objects like CMyApp handle menu commands. Many programmers don’t even realize how out of the ordinary this is. If you already handle ID_APP_ABOUT in your application object, why should you have to implement a separate mechanism if you add ID_APP_ABOUT to your system menu?

A better and more MFC-like way to handle add-on system commands would be to pass them through the normal command-routing mechanism. Then you could handle system commands the normal MFC way, by writing ON_COMMAND handlers. You could even use ON_UPDATE_COMMAND_UI to update your system menu items, for example to disable an item or display a checkmark next to it.

Figure 2 shows a little class I wrote, CSysCmdRouter, that turns system commands into ordinary commands. To use it, all you have to do is instantiate CSysCmdRouter in your main frame and call its Init method from OnCreate:

int CMainFrame::OnCreate(...) { 
// Add my items to system menu CMenu* pMenu = GetSystemMenu(FALSE); pMenu->AppendMenu(..ID_MYCMD1..); pMenu->AppendMenu(..ID_MYCMD2..); 
// Route sys commands through MFC m_sysCmdHook.Init(this); return 0; }
 
// MSDN Magazine May 2005 
// If this program works, it was written by Paul DiLascia. 
// If not, I don't know who wrote it. 
// #include "Subclass.h" 
// 
// Class to route user-defined WM_SYSCOMMAND messages through the normal 
// MFC command routing system, so you can handle them the normal MFC way 
// with ON_COMMAND and ON_UPDATE_COMMAND_UI handlers. The simplest way to 
// achieve this is to translate the system commands to ordinary WM_COMMAND
// messages. // // To use: instantiate in your CMainFrame and call Init from OnCreate. 
// 
// You must also link Subclass.cpp in your app. 
// class CSysCmdRouter : public CSubclassWnd { protected: CWnd* m_pMainWnd; 
// main window hooked public: CSysCmdRouter() { } virtual ~CSysCmdRouter() { } 
// Initialize: hook the main window BOOL Init(CWnd* pMainWnd) { ASSERT(pMainWnd); 
m_pMainWnd = pMainWnd; return HookWindow(pMainWnd); } 
// Terminate: unhook. No need to call unless you want to stop hooking 
// for some reason; CSubclassWnd will automatically unhook itself when 
// the hooked window is destroyed. void Term() { Unhook(); } protected: 
// virtual WndProc handler: convert WM_SYSCOMMAND to WM_COMMAND if the 
// command is not a system command: That is, if the command ID is less 
// than SC_SIZE = 0xFFFF. 
// virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp) { 
if (msg==WM_SYSCOMMAND && wp<SC_SIZE) { return m_pMainWnd->SendMessage(WM_COMMAND, wp, NULL); } else if (msg==WM_INITMENUPOPUP) { 
// Hide system menu flag (= HIWORD(lp)) so even system menu can 
// be initialized through MFC. This is somewhat dangerous 
// because you lose the ability to distinguish between the 
// system menu and other menus—but if you're adding your own 
// commands it shouldn't matter which menu(s) they come from! 
// Just make sure your command IDs don't conflict with the 
// built-in commands like SC_SIZE,and so on, which start at 
// 0xF000. By default, MFC command IDs start at 0x8000 so you'll 
// be safe if you follow MFC. lp = LOWORD(lp); } 
return CSubclassWnd::WindowProc(msg, wp, lp); 
// pass along //-important! 
} 
};

Once you call CSysCmdRouter::Init, you can process ID_MYCMD1 and ID_MYCMD2 the normal way, by writing ON_COMMAND handlers for any object in the MFC command-routing superhighway—view, document, frame, app, or any other command target you’ve added by overriding OnCmdMsg. CSysCmdRouter also lets you update your system menu using ON_UPDATE_COMMAND_UI handlers. The only caveat is to make sure your command IDs don’t conflict with any other menu commands (unless they truly represent the same command) or any of the built-in system commands, which begin at SC_SIZE = 0xF000. Visual Studio® .NET assigns command IDs starting at 0x8000 = 32768, so if you let Visual Studio assign the IDs, you’ll be OK as long as you don’t have more than 0xF000-0x8000 = 0x7000 commands. That’s 28,672 in base 10. If your app has more than 28,000 commands, you need to consult a programming psychiatrist.

How does CSysCmdRouter perform its magic? Simple: it uses the ubiquitous CSubclassWnd from my previous columns. CSubclassWnd lets you subclass MFC window objects without deriving from them. CSysCmdRouter derives from CSubclassWnd and uses it to subclass the main frame. Specifically, it intercepts WM_SYSCOMMAND messages sent to the frame. If the command ID is in the system command range (greater than SC_SIZE = 0xF000), CSysCmdRouter passes it along to Windows; otherwise it eats the WM_SYSCOMMAND and resends it as a WM_COMMAND, whereupon MFC follows its normal routing procedures, calling your ON_COMMAND handlers. Pretty clever, eh?

And what about ON_UPDATE_COMMAND_UI handlers? How does CSysCmdRouter make them work for system menu commands? Simple. Just before Windows displays a menu, it sends your main window a WM_INITMENUPOPUP message. This is your big chance to update the menu items—enable or disable them, add checkmarks, and so on. MFC catches WM_INITMENUPOPUP in CFrameWnd::OnInitMenuPopup and performs its UI-update magic. MFC creates a CCmdUI object for each menu item and passes it to the appropriate ON_UPDATE_COMMAND_UI handlers in your message maps. The MFC function that takes care of this is CFrameWnd::OnInitMenuPopup, which begins like so:

void CFrameWnd::OnInitMenuPopup(CMenu* pMenu, UINT nIndex, BOOL bSysMenu) { 
if (bSysMenu) return; 
// don't support system menu ... 
}

MFC doesn’t do anything to initialize the system menu. Why not? Why should it care? What if somehow you could make bSysMenu FALSE, even for the system menu? That’s exactly what CSysCmdRouter does. It intercepts WM_INITMENUPOPUP and clears the bSysMenu flag, which is the HIWORD of LPARAM:

if (msg==WM_INITMENUPOPUP) { lp = LOWORD(lp); // (set HIWORD = 0) }

Now when MFC gets WM_INITMENUPOPUP, it thinks the menu is a normal menu. This all works fine as long as your command IDs don’t conflict with true system commands. The only thing you lose is the ability to distinguish the system menu from the main window menu if you override OnInitMenuPopup. Hey, you can’t have everything! You can always handle WM_INITMENUPOPUP by overriding CWnd::WindowProc, or compare HMENUs if you need to distinguish. But really, you shouldn’t care where the command came from.
Task Bar Menu
To show how all this works in practice, I wrote a little test program called TBMenu. Figure 3 shows the menu displayed when you right-click on TBMenu’s minimized button in the task bar. You can see the two extra commands at the bottom of the menu. Figure 4 shows the code for TBMenu’s CMainFrame. You can see where it adds the commands in OnCreate and handles them with ON_COMMAND and ON_UPDATE_COMMAND_UI handlers in CMainFrame’s message map. TBMenu handles ID_APP_ABOUT within its application class (not shown). CSysCmdRouter makes system commands work like any other commands. Speaking of commands, see the fascinating sidebar “Origins of Ctrl+Alt+Del”.

// MSDN Magazine May 2005 
// If this program works, it was written by Paul DiLascia. 
// If not, I don't know who wrote it. 
// #include "StdAfx.h" 
#include "TbMenu.h" 
#include "MainFrm.h" ... 
IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd) 
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) 
ON_WM_CREATE() ON_COMMAND(ID_CHECKME, OnCheckMe) 
ON_UPDATE_COMMAND_UI(ID_CHECKME, OnUpdateCheckMe) 
END_MESSAGE_MAP() 
CMainFrame::CMainFrame() : m_bChecked(0) { } 
CMainFrame::~CMainFrame() { } 
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { 
... 
// normal MFC stuff: call base class OnCreate, 
... 
// create toolbar and status bar (not shown here) 
// Append my own items to the system menu CMenu* pMenu = GetSystemMenu(FALSE); ASSERT(pMenu!=NULL); 
pMenu->AppendMenu(MF_BYPOSITION|MF_SEPARATOR); 
pMenu->AppendMenu(MF_STRING,(UINT_PTR)ID_CHECKME, _T("Chec&k Me")); 
pMenu->AppendMenu(MF_STRING,(UINT_PTR)ID_APP_ABOUT, _T("&About TBMenu")); 
// Hook system commands so they're routed through MFC 
// command routing to ON_COMMAND handlers.
m_sysCmdHook.Init(this); return 0; } 
// 
// Handle "Check Me" command 
// void CMainFrame::OnCheckMe() { 
m_bChecked = !m_bChecked; 
MessageBox(m_bChecked ? _T("Checked") : _T("Unchecked"), _T("TBMenu")); } 
// 
// Set checkmark next to "Check Me" command (or not) 
// void CMainFrame::OnUpdateCheckMe(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_bChecked); }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值