C++ Q&A

Calling Virtual Functions, Persisting View State, POD Type

Paul DiLascia

Code download available at: CQA0411.exe (189 KB)
Browse the Code Online

Q In C++, you can't successfully call a derived virtual function from a constructor because the vtable isn't fully set up. But it appears you can do this in C#. Is this correct? Why is C# different?

Q In C++, you can't successfully call a derived virtual function from a constructor because the vtable isn't fully set up. But it appears you can do this in C#. Is this correct? Why is C# different?

Clifton F. Vaughn

A Indeed, C# behaves differently from C++ in this respect. In C++, if you call a virtual function from a constructor or destructor, the compiler calls the instance of the virtual function defined for the class being constructed (for example, Base::SomeVirtFn if called from Base::Base), not the most derived instance. As you say, this is because the vtable is not fully initialized until the most derived constructor executes. Another way to think of it is that the derived class is not created yet.

A Indeed, C# behaves differently from C++ in this respect. In C++, if you call a virtual function from a constructor or destructor, the compiler calls the instance of the virtual function defined for the class being constructed (for example, Base::SomeVirtFn if called from Base::Base), not the most derived instance. As you say, this is because the vtable is not fully initialized until the most derived constructor executes. Another way to think of it is that the derived class is not created yet.

Figure 2 Virtual Function Test

Figure 2** Virtual Function Test **

Similarly, when you call a virtual function from a destructor, C++ calls the base class function because the derived class has already been destroyed (its destructor has been called). While this behavior can lead to unexpected results (which is why it's considered bad programming practice to call a virtual function from a constructor or destructor), it is at least familiar to most C++ programmers and makes some sense.

As you point out, things are different in C#. Managed objects—whether in C#, managed C++, or any other .NET-compliant language—are created as their final type, which means that if you call a virtual function from a constructor or destructor, the system calls the most derived function. Figure 1 shows a program that illustrates this. If you compile and run it, you'll see the output shown in Figure 2.

Figure 1 VirtFnTest

////////////////////////////////////////////////////////////////
// MSDN Magazine — November 2004

// To compile:
//   cl /clr VirtFnTest.cpp
//
#include <stdio.h>
#include <vcclr.h>
#using <mscorlib.dll>

using namespace System;

/////////////////
// C++ native base class. Ctor calls virtual fn: always calls Base fn.
class Base {
public:
   Base()
   {
      printf(" Base::ctor\n");
      Hello();
   }
   ~Base()
   {
      printf(" Base::dtor\n");
      Goodbye();
   }
   virtual void Hello()   { printf(" Base::Hello\n"); }
   virtual void Goodbye() { printf(" Base::Goodbye\n"); }
};

// Derived native C++ class overrides fn, but not called from ctor.
class Derived : public Base {
public:
   Derived()
   {
      printf(" Derived::ctor\n");
   }
   ~Derived()
   {
      printf(" Derived::dtor\n");
   }
   virtual void Hello()   { printf(" Derived::Hello\n"); }
   virtual void Goodbye() { printf(" Derived::Goodbye\n"); }
};

// Managed base class. Ctor calls virtual fn: always calls most derived 
// override.
public __gc class MBase {
public:
   MBase() {
      printf(" Base::ctor\n");
      Hello();
   }
   ~MBase() {
      printf(" Base::dtor\n");
      Goodbye();
   }
   virtual void Hello()   { printf(" Base::Hello\n"); }
   virtual void Goodbye() { printf(" Base::Goodbye\n"); }
};

// Managed derived class overrides virtual fn.
public __gc class MDerived : public MBase {
public:
   MDerived()
   {
      printf(" Derived::ctor\n");
   }
   ~MDerived()
   {
      printf(" Derived::dtor\n");
   }
   virtual void Hello()   { printf(" Derived::Hello\n"); }
   virtual void Goodbye() { printf(" Derived::Goodbye\n"); }
};

int main()
{
   printf("Create native object:\n");
   Derived *pd = new Derived();
   printf("Destroy native object:\n");
   delete pd;

   printf("\nCreate managed object:\n");
   MDerived *pmd = new MDerived();
   printf("Destroy managed object:\n");
   delete pmd; // force dtor call
   return 0;
}

This behavior may seem bizarre to C++ programmers. It means you can call a virtual function for a derived type before the derived type has been initialized—that is, before its constructor has run. Likewise, if you call a virtual function from the base destructor, the function runs after the derived class has been destroyed—that is, after the destructor is called. So once again—but for different reasons—it's considered bad practice to call a virtual function from a constructor or destructor.

Why did the Redmontonians choose to design C# this way? Because it simplifies memory management. The garbage collector needs to know how big an object is in order to free it. If C# constructed objects the way C++ does, you could end up with a situation in which you have two objects, Obj1 and Obj2, where these two statements are both true

typeof(Obj1)==typeof(Obj2) 
sizeof(Obj1)!= sizeof(Obj2) 

because one of the objects is partially constructed. (Don't forget the garbage collector runs asynchronously.) By constructing objects as their final type, the garbage collector can determine an object's size from its type. If C# did partial construction à la C++, the garbage collector would need more code to determine the true size of a partially constructed object. This would add complexity and slow things down, all to handle a situation that's discouraged in the first place. So in the interest of a faster garbage collector, the Redmondtonians decided to implement C# as described. For a discussion of this, see Raymond Chen's blog, "The Old New Thing".

Q In your March 2004 column, you showed how to change the last view setting in the file open dialog, but didn't cover the part about saving the last view used by the user. The problem I encountered is how to read the setting the user has made in the file open dialog. The only way I found was reading the list control directly, but that doesn't give me the correct information when the user selected thumbnail view. Do you have any solution for this?

Q In your March 2004 column, you showed how to change the last view setting in the file open dialog, but didn't cover the part about saving the last view used by the user. The problem I encountered is how to read the setting the user has made in the file open dialog. The only way I found was reading the list control directly, but that doesn't give me the correct information when the user selected thumbnail view. Do you have any solution for this?

Maarten van Dillen

Q I'm trying to do something with the common CFileDialog class that should be trivial, but it does not seem to be. I want to force the view mode in the file open dialog box to be thumbnail view. Can you suggest a way to do this Visual C++®?

Q I'm trying to do something with the common CFileDialog class that should be trivial, but it does not seem to be. I want to force the view mode in the file open dialog box to be thumbnail view. Can you suggest a way to do this Visual C++®?

Elliot Leonard

A Several readers have asked questions about thumbnail view in the file open dialog. In my March column, I showed how to send WM_COMMAND messages to the special SHELLDLL_DefView window in the file open dialog to set various view modes—but how can you tell which mode the view is currently in? To discover the view mode, you have to get the list control and call CListCtrl::GetView:

// in dialog class
HWND hlc = ::FindWindowEx(m_hWnd, 
  NULL, _T("SysListView32"), NULL);
CListCtrl* plc =  (CListCtrl*)CWnd::FromHandle(hlc);
DWORD dwView = plc->GetView();

CListCtrl::GetView returns one of the LV_XXX codes, but as Maarten discovered, Windows® returns LV_VIEW_ICON for both icon and thumbnail views.

A Several readers have asked questions about thumbnail view in the file open dialog. In my March column, I showed how to send WM_COMMAND messages to the special SHELLDLL_DefView window in the file open dialog to set various view modes—but how can you tell which mode the view is currently in? To discover the view mode, you have to get the list control and call CListCtrl::GetView:

// in dialog class
HWND hlc = ::FindWindowEx(m_hWnd, 
  NULL, _T("SysListView32"), NULL);
CListCtrl* plc =  (CListCtrl*)CWnd::FromHandle(hlc);
DWORD dwView = plc->GetView();

CListCtrl::GetView returns one of the LV_XXX codes, but as Maarten discovered, Windows® returns LV_VIEW_ICON for both icon and thumbnail views.

So how can you distinguish between them? I scratched my head pondering this one and spent a while poking through the header files until I discovered a message called LVM_GETITEMSPACING that fetches the—what else?—icon spacing. As the name suggests, the icon spacing is the number of pixels between icons in icon view. LVM_GETITEMSPACING is so obscure that MFC doesn't have a wrapper for it (there's no CListCtrl::GetIconSpacing). So in MFC, you have to send the message yourself:

CSize sz = CSize(plc->SendMessage(LVM_GETITEMSPACING));

Windows returns the size the usual way, as a long with cx/cy encoded in the high- and low-order words, which CSize politely decodes for you. Once you have the icon spacing, you can compare it to the system spacing returned from GetSystemMetrics(SM_CXICONSPACING). You mean that wasn't obvious? If the list view's icon spacing is the same as the system spacing, the list view is in icon mode. If it's bigger, the view's in thumbnail mode:

if (sz.cx > GetSystemMetrics(SM_CXICONSPACING)) {        
  // thumbnail view
} else {
  // icon view 
}

So much for thumbnail view. Next, how do you persist the view state across user sessions? For that, you need the Profile functions to save the last-used mode in the user's profile when your program terminates and restore it again the next time it starts up. I wrote a little program, DlgTest, that shows how (see the code download at the MSDN®Magazine Web site). It has a class CPersistOpenDlg that implements the persistent behavior. CPersistOpenDlg uses another class, CListViewShellWnd, to encapsulate the SHELLDLL_DefView window (see my March 2004 column). CListViewShellWnd has functions to set and get the view mode; these functions differentiate icon from thumbnail view:

CListViewShellWnd m_wndLVSW;
•••
m_wndLVSW.SetViewMode(ODM_VIEW_THUMBS);

CListViewShellWnd's OnDestroy handler saves the view mode in a data member, m_lastViewMode. CPersistOpenDlg's destructor calls WriteProfileInt to write this value to the user's profile as the dialog is destroyed. When the dialog starts up, CPersistOpenDlg posts an initialization message to itself; the handler for this message calls GetProfileInt to read the stored value from disk and set the view mode. PostMessage is required because the normal initialization messages, WM_INITDIALOG and CDN_INITDONE, come before the file open dialog is fully initialized—for an explanation of this, please read my March 2004 column.

By the way, you should use GetProfileXxx and WriteProfileXxx any time you want to persist your application settings across program invocations. MFC has CWinApp wrappers for these API functions. If you call CMyApp::SetRegistryKey("KeyName") when your application starts up (typically in your InitInstance function), MFC uses the registry for profile settings instead of an INI file. Here's the INI file for DlgTest:

[settings]
ViewMode=28717

Q I've seen the term "POD type" occasionally in literature and documentation about C++ and the Microsoft® .NET Framework. What is a POD type?

Q I've seen the term "POD type" occasionally in literature and documentation about C++ and the Microsoft® .NET Framework. What is a POD type?

Shelby Nagwitz

A You might think a POD type is a data type that arrives from outer space wrapped in a green protective covering, but POD stands for Plain Old Data and that's just what a POD type is. The exact definition is rather gnarly (see the C++ ISO standard), but the basic idea is that POD types contain primitive data compatible with C. For example, structs and ints are POD types, but a class with a user-defined constructor or virtual function is not. POD types have no virtual functions, base classes, user-defined constructors, copy constructors, assignment operator, or destructor.

A You might think a POD type is a data type that arrives from outer space wrapped in a green protective covering, but POD stands for Plain Old Data and that's just what a POD type is. The exact definition is rather gnarly (see the C++ ISO standard), but the basic idea is that POD types contain primitive data compatible with C. For example, structs and ints are POD types, but a class with a user-defined constructor or virtual function is not. POD types have no virtual functions, base classes, user-defined constructors, copy constructors, assignment operator, or destructor.

To conceptualize POD types you can copy them by copying their bits. Also, POD types can be uninitialized. For example:

struct RECT r; // value undefined
POINT *ppoints = new POINT[100]; // ditto
CString s; // calls ctor ==> not POD

Non-POD types usually require initialization, whether by calling a default (compiler-supplied) constructor or your own.

In the old days, POD was important mostly for people writing compilers or C++ programs compatible with C. Nowadays, POD comes up in the context of .NET. In managed C++, managed types (both __value and __gc) can contain embedded native POD types. Figure 3 shows code that illustrates this. The managed Circle class can contain a POINT but not a CPoint. If you try to compile pod.cpp, you'll get error C3633: "Cannot define 'm_center' as a member of managed 'Circle' because of the presence of default constructor 'CPoint::CPoint' on class 'CPoint'."

Figure 3 Pod.cpp

/////////////////////////////////////////////////////////////////////
// You can embed native POD types in a managed (__gc or __value) type.
// Compile with cl /clr.

#using <mscorlib.dll>

// simple struct is a POD type
struct POINT {
   int x;
   int y;
};

// native class w/ctor: not POD!
class CPoint : public POINT {
   CPoint() { x=y=0; }
};

// Managed __gc type
__gc class Circle {
public:
   POINT center;        // ok: embedded POD type
   CPoint m_center;     // Error: not POD!
   int radius;
};

Figure 3 Pod.cpp

The reason .NET restricts embedded native objects to POD types is so that it can safely copy them without worrying about calling constructors, initializing vtables, or any of the other machinations that non-POD types require.

Send your questions and comments for Paul to  cppqa@microsoft.com.

Paul DiLascia is a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at https://www.dilascia.com.