rainbowzc

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: :: :: 管理 ::
Kenny Kerr

This article is based on a prerelease version of the Visual C++ Feature Pack. All information herein is subject to change.
This article discusses:
  • Microsoft Foundation Classes updates
  • Programming the Ribbon in C++
  • Tabbed MDIs in C++
  • Polymorphic functions and smart pointers
This article uses the following technologies:
Visual Studio 2008, MFC
As a developer using Visual C++ , you may have felt a bit left behind in recent years as it seems like Microsoft has added more new features and functionality to Visual C#® than to Visual C++®. The truth is that although the Visual C++ compiler has continued to improve in a variety of areas including performance, security, and standards conformance, little has been done in the way of new library and productivity features for quite some time. And while MFC was updated to better support Windows Vista®, more could have been done.
Now, however, to better support developers who use native code and MFC in particular, Microsoft has released the Visual C++ 2008 Feature Pack. Here's your evidence of a renewed commitment to Visual C++.
The feature pack includes a large set of new MFC classes for building modern user interfaces. It also includes a significant amount of the functionality being added to the Standard C++ Library as part of Technical Report 1 (TR1). TR1 is the first major update and addition to the Standard C++ Library adopted by the C++ committee.
The traditional paradigms of single and multiple document/view applications, menus, toolbars and dialogs have been a staple of MFC development for years. If you wanted your MFC application to look a little more modern, you'd be on your own.
That's all changed. MFC now includes many new user interface paradigms including dockable panes resembling those found in Microsoft® Office and Visual Studio®. It also includes full support for the Microsoft Office Ribbon user interface as well as many other new controls, dialog boxes and windows.
Next, I'm going to demonstrate two of the new user interface features in MFC: the Office Ribbon and the Tabbed Multiple Document Interface (MDI).

Office Ribbon User Interface
By now, I'm certain you've seen the new 2007 Microsoft Office system Ribbon element, and you may have wondered how to create that look in your own applications. The good news is that it is surprisingly easy to add a Ribbon bar to an MFC frame window.
Much of the new functionality relies on new versions of the CWinApp, CFrameWnd, and CMDIFrameWnd classes; these classes represent the foundation for most MFC applications. CWinAppEx derives from CWinApp and should be used as the base class for your application object. CFrameWndEx derives from CFrameWnd and should be used as the base class for your single document interface (SDI) frame window. Similarly, CMDIFrameWndEx derives from CMDIFrameWnd and should be used as the base class for your MDI frame windows. These new base classes provide all of the plumbing necessary to support many of the new user interface facilities such as dockable and resizable window panes as well as workspace persistence.
Figure 1 shows a minimal application object that can support a Ribbon bar. As you can see, the Application class derives from CWinAppEx and implements the familiar InitInstance member function that's traditionally used to create the application's main window. You must remember to call the SetRegistryKey member function to set the registry location for application settings since the frame classes rely on it. InitInstance then proceeds to create the main window in the usual manner.
The code in Figure 2 shows a minimal SDI frame window with a Ribbon bar and application button. The application button is not required but is typically used in conjunction with a Ribbon bar to provide a main menu of sorts for the application in lieu of the traditional File menu.
Conceptually, a Ribbon consists of a number of tabs known as categories that each host a group of panels. These panels, in turn, host the Ribbon elements or controls that represent the various operations specific to your application. If a Ribbon hosts an application button (the large circular button in the top-left corner), then the popup window displayed when the user clicks on the application button also displays a panel that is considered the Ribbon's main category.
The CMFCRibbonBar class implements the Ribbon bar itself and the CMFCRibbonApplicationButton class represents the application button hosted by the Ribbon bar and displayed in the top-left corner of the window frame. The Ribbon bar is typically created and prepared inside your WM_CREATE message handler. To create the Ribbon bar, simply call CMFCRibbonBar's Create member function providing the address of the window frame to attach it to. You can then populate it as you wish. The AddMainCategory member function adds the main category to a Ribbon and returns a pointer to the CMFCRibbonMainPanel to which you can add the Ribbon elements that will be featured on this panel.
More categories can be added to represent the Ribbon's tabs by calling the AddCategory member function. AddCategory returns a pointer to a CMFCRibbonCategory object to which you can add panels using its AddPanel member function. AddPanel returns a pointer to a CMFCRibbonPanel object to which you can add Ribbon elements just like you can with the Ribbon's main panel. Finally, you can set the visual manager that is responsible for the style and appearance of the frame window using the CMFCVisualManager::SetDefaultManager static member function. Figure 3 shows you what the Ribbon application might look like, assuming you've added the necessary event handlers for the buttons on the Ribbon bar.
Figure 3 Ribbon Sample Application 

Tabbed Multiple Document Interface
MFC has long supported an MDI implementation with its document/view architecture, but the traditional MDI you see in Figure 4 looks so outdated, your users may think your application hasn't been updated since Windows® 95. Today, the majority of users expect multiple documents to be accessible through tabs along the edge of a window and this is exactly what the new CMDIFrameWndEx MDI frame window provides.
0) this.src=small; if (current.indexOf(small) > 0)this.src=large;" alt="" src="http://i.msdn.microsoft.com/cc507634.fig4.gif">
Figure 4 The Windows Stone Age (Click the image for a larger view)
You'll need to update your multiple document/view application object to support the new frame window. Figure 5 shows a minimal application object that will suffice. It is similar to a traditional MDI application object, but a few points are worth mentioning.
class Application : public CWinAppEx
{
DECLARE_MESSAGE_MAP()
public:
virtual BOOL InitInstance();
};
BEGIN_MESSAGE_MAP(Application, CWinAppEx)
ON_COMMAND(ID_FILE_NEW, &CWinAppEx::OnFileNew)
END_MESSAGE_MAP()
BOOL Application::InitInstance()
{
SetRegistryKey(L"SampleCompany\\SampleProduct");
VERIFY(InitContextMenuManager());
AddDocTemplate(new CMultiDocTemplate(IDR_CHILDFRAME,
RUNTIME_CLASS(Document),
RUNTIME_CLASS(CMDIChildWndEx),
RUNTIME_CLASS(View)));
MainWindow* mainWindow = new MainWindow();
VERIFY(mainWindow->LoadFrame(IDR_MAINFRAME));
m_pMainWnd = mainWindow;
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
First, the runtime class for the child window frame is CMDIChildWndEx instead of the traditional CMDIChildWnd class. The InitContextMenuManager function is also called to prepare the menu manager used when switching between the tabbed views.
Figure 6 shows a minimal MDI frame window. Again, you should be pleased to see how little is involved in turning on this functionality. All that's really needed is a call to the EnableMDITabbedGroups member function to enable the MDI tabbed groups feature. The CMDITabInfo class provides a variety of member variables that you can use to customize the appearance and behavior of the tabbed groups. And as its name implies, it even allows the user to drag different views to create groups of tabs either vertically or horizontally aligned. Figure 7 shows what it might look like.
0) this.src=small; if (current.indexOf(small) > 0)this.src=large;" alt="" src="http://i.msdn.microsoft.com/cc507634.fig7.gif">
Figure 7 Modern Tabbed MDI Application (Click the image for a larger view)

What's New in the Standard C++ Library
As I've mentioned, the feature pack also includes significant additions to the Standard C++ Library as a part of TR1. This includes support for reference-counted smart pointers, polymorphic function wrappers, hashtable-based containers, regular expressions, and much more. In the following scenarios, I'll take a look at some of these new TR1 features.

Polymorphic Function Objects
The ability to refer to a function as a value that can be passed as a parameter or stored for later use is a vital part of many applications. This concept can be used to implement various common constructs including callback functions, event handlers and asynchronous programming facilities. Functions, however, can be quite unwieldy in C++. The design of functions was primarily driven by the need for compatibility with C and for great performance. Although these goals were achieved, it didn't make it any easier to treat functions as objects that can be stored, passed around and ultimately called asynchronously. Let's take a look at a few common function-like constructs in C++.
First, there is the good old non-member function:
int Add(int x, int y)
{
return x + y;
}
Naturally, it can be called as follows:
int result = Add(4, 5);
ASSERT(4 + 5 == result);
Another common function-like construct is the function object, or functor:
class AddFunctor
{
public:
int operator()(int x, int y) const
{
return x + y;
}
};
Since it implements the call operator, a function object can be used as if it were a function:
   AddFunctor fo;
int result = fo(4, 5);
ASSERT(4 + 5 == result);
Then there is the non-static member function:
   class Adder
{
public:
int Add(int x, int y) const
{
return x + y;
}
};
Of course, calling the member function requires an object:
Adder adder;
int result = adder.Add(4, 5);
ASSERT(4 + 5 == result);
So far, so good. Now let's say you need to store these function-like constructs for later use. You can define a type that is capable of storing a pointer to a non-member function as follows:
typedef int (*FunctionPointerType)(int x, int y);
A function pointer can also be used as if it were a function:
FunctionPointerType fp = &Add;
int result = fp(4, 5);
ASSERT(4 + 5 == result);
Although the function object can also be stored, it cannot be stored polymorphically along with a function pointer.
The member function can be stored in a pointer-to-member-function:
Adder adder;
typedef int (Adder::*MemberFunctionPointerType)(int x, int y);
MemberFunctionPointerType mfp = &Adder::Add;
But the pointer-to-member-function type is not compatible with the pointer-to-non-member-function type and thus cannot be stored polymorphically alongside its non-member function rival. Even if that were possible, the member function still would require an object to provide the context for the member function call:
int result = (adder.*mfp)(4, 5);
ASSERT(4 + 5 == result);
I could go on, but I think you get the idea. Fortunately, a solution is provided by the new tr1::function class template. The tr1::function class template holds a callable object for the function type defined in its template parameter. Here I'm initializing it with the non-member function:
function<int (int x, int y)> f = &Add;
int result = f(4, 5);
ASSERT(4 + 5 == result);
You can just as easily initialize it with the function object:
function<int (int x, int y)> f = AddFunctor();
You can even use the new function binding facility to initialize it with the member function:
function<int (int x, int y)> f = bind(&Adder::Add, &adder, _1, _2);
I'll talk about the bind function in a moment but the beauty here is that now there is a single function wrapper that you can bind to non-member functions, function objects, or even member functions. You can store it and call it at some point in the future, and it's all done polymorphically.
The function wrapper is also rebindable and can be set to nothing just like normal function pointers:
function<int (int x, int y)> f;
ASSERT(0 == f);
f = &Add;
ASSERT(0 != f);
f = bind(&Adder::Add, &adder, _1, _2);
The bind function template provides a much more powerful alternative to the function object adapters in the Standard C++ Library—specifically std::bind1st() and std::bind2nd(). In this example, the first parameter of bind is the address of the member function. The second parameter is the address of the object on which the member is to be called. The last two parameters in this example define placeholders that will be resolved when the function is called.
Of course, bind is not limited to member functions. You can create a squaring function by binding the Standard C++ Library's multiplies function object to produce a function with a single parameter that will produce the result of the parameter squared:
function<int (int)> square = bind(multiplies<int>(), _1, _1);
int result = square(3);
ASSERT(9 == result);
Note that the tr1::function class template works great with Standard C++ Library algorithms. Given a container of integers, you can produce the sum of all the values using the member function, as follows:
function<int (int x, int y)> f = // initialize
int result = accumulate(numbers.begin(),
numbers.end(),
0, // initial value
f);
Keep in mind that the tr1::function class template can inhibit compiler optimizations (such as inlining) that may be possible if you simply use a function pointer or function object directly. Therefore, you should only use the tr1::function class template when necessary, such as in the case of algorithms like accumulate where it may be called repeatedly. When possible, you should pass function pointers, member function pointers (adapted with mem_fn from TR1), and function objects (such as those returned by bind) directly to Standard C++ Library algorithms and other templated algorithms.
Let's not stop there. Here's a more interesting problem. Say you have a Surface class representing some drawing surface and a Shape class that is capable of drawing itself onto the surface:
class Surface
{
//...
};
class Shape
{
public:
void Draw(Surface& surface) const;
};
Now consider how you might draw each shape in a container onto a given surface. You might want to use the for_each algorithm, as shown in the following:
Surface surface = // initialize
for_each(shapes.begin(),
shapes.end(),
bind(&Shape::Draw, _1, surface)); // wrong
Here I'm taking advantage of the bind function template to invoke the member function on each element of the shapes container, binding the surface as the parameter to the Draw member function. Unfortunately, depending on how Surface is defined, it may not work as expected or even compile. The trouble is that the bind function template will try to make a copy of surface when what you really want is a reference. Fortunately, TR1 also introduces the reference_wrapper class template that allows you to treat a reference as a value that may be freely copied. The ref and cref function templates simplify the creation of reference_wrapper objects thanks to their type inference.
With the help of reference_wrapper, the for_each algorithm is now able to successfully draw the shapes to the surface both simply and efficiently:
for_each(shapes.begin(),
shapes.end(),
bind(&Shape::Draw, _1, ref(surface)));
As you can imagine, the new function wrappers, binding features, and reference wrappers can be combined in a myriad of ways to solve all kinds of problems elegantly.

Smart Pointers
Smart pointers are an essential tool for C++ developers. I routinely use ATL's CComPtr for handling COM interface pointers and the Standard C++ Library's auto_ptr for handling raw C++ pointers. The latter is useful for scenarios where you need to dynamically create C++ objects and ensure the object is safely deleted when the auto_ptr object goes out of scope.
As useful as auto_ptr is, it can only be used safely in a few scenarios. This is because of the transfer-of-ownership semantics that it implements. That is, if you copy or assign an auto_ptr object, the ownership of the underlying resource is transferred and lost by the originating auto_ptr object. Although this is clearly useful in scenarios where you have fine-grained control over resource allocations, there are many scenarios where you might need to share objects, and a smart pointer implementing shared-ownership semantics would be very useful indeed. More importantly, auto_ptr cannot be used with Standard C++ Library containers.
TR1 introduces two new smart pointers that work together to provide a multitude of uses. The shared_ptr class template works in much the same way as auto_ptr but instead of transferring ownership of a resource it merely increases a reference count on the resource. When the last shared_ptr object that holds a reference to the object is destroyed or reset, the resource is automatically deleted. The weak_ptr class template works with shared_ptr to allow callers to reference a resource without affecting its reference count. This is useful for object models where you might have cyclical relationships or perhaps if you're implementing a caching service. It also works great with Standard C++ Library containers!
As a comparison, consider the following use of auto_ptr:
auto_ptr<int> ap(new int(123));
ASSERT(0 != ap.get());
// transfer ownership from ap to ap2
auto_ptr<int> ap2(ap);
ASSERT(0 != ap2.get());
ASSERT(0 == ap.get());
The auto_ptr copy constructor transfers the ownership from ap to ap2. The behavior of shared_ptr is equally predictable:
shared_ptr<int> sp(new int(123));
ASSERT(0 != sp);
// increase reference count of shared object
shared_ptr<int> sp2(sp);
ASSERT(0 != sp2);
ASSERT(0 != sp);
Internally, all shared_ptr objects that refer to the same resource share a control block that tracks the number of shared_ptr objects that jointly own the resource as well as the number of weak_ptr objects that refer to it. I'll show you how to use the weak_ptr class template in a moment.
Member functions similar to auto_ptr are provided by shared_ptr. They include the dereference and arrow operators, the reset member function to replace the resource, and the get member function that returns the address of the resource. A few unique member functions, including one coincidentally named unique, are also provided. The unique member function tests whether the shared_ptr object is the only smart pointer to hold a reference to the resource. Here's an example:
shared_ptr<int> sp(new int(123));
ASSERT(sp.unique());
shared_ptr<int> sp2(sp);
ASSERT(!sp.unique());
ASSERT(!sp2.unique());
You can also get the number of shared_ptr objects that own the resource with the use_count member function:
shared_ptr<int> sp;
ASSERT(0 == sp.use_count());
sp.reset(new int(123));
ASSERT(1 == sp.use_count());
shared_ptr<int> sp2(sp);
ASSERT(2 == sp.use_count());
ASSERT(2 == sp2.use_count());
You should, however, limit the use of use_count to debugging only as it is not guaranteed to be a constant time operation on all implementations. Note that you can rely on the provided operator unspecified-bool-type to determine whether the shared_ptr owns anything, and you can use the unique function to determine whether the shared_ptr is the unique owner of something.
The weak_ptr class template stores a weak reference to a resource owned by a shared_ptr object. Once all of the shared_ptr objects owning the resource are destroyed or reset, the resource is deleted regardless of whether any weak_ptr objects are referring to it. To ensure that you cannot use a resource referred to only by a weak_ptr object, the weak_ptr class template does not provide the familiar get member function to return the resource's address or the member access operator. Instead, you need to first convert the weak reference into a strong reference in order to access the resource. This functionality is provided by the lock member function, as shown in Figure 8.
If in the interim the resource is released, the weak_ptr object's lock member function will return a shared_ptr object that does not own a resource. As you can probably imagine, shared_ptr and weak_ptr can dramatically simplify resource management in many applications.
It's pretty clear that Visual C++ 2008 Feature Pack is a welcome upgrade to the Visual C++ libraries and is sure to come in handy! There's so much more to explore but I hope that this introduction has piqued your interest and that you'll set aside a few moments to take a closer look yourself.

Kenny Kerr is a software craftsman specializing in software development for Windows. He has a passion for writing and teaching developers about programming and software design. You can reach Kenny at his blog, weblogs.asp.net/kennykerr.

posted on 2008-07-03 00:51  ct  阅读(1007)  评论(0编辑  收藏  举报