Virtual functions but no virtual variables in classes?

Pages: 12
Hi, I am new to C++, picking it up to dabble in some windows mobile programming.

I am using the excellent documentation on this site, and I have come up against something I do not see the solution for, so I thought I would ask.

I am writing an app to display multiple widgets on the screen, pulled from an INI or XML file. I have a Widget class with member variables of x and y coordinates and name of the widget.

Then I have derived classes for different kinds of widget (eg clock, image, etc), each of which would have their own member variables (eg an image widget would have filename but clock would not) that the base Widget class does not.

I want to effectively put all the Widgets in a global array for rendering, regardless of which sublcass they are, so I was thinking an array of pointers, but obviously I would need it to be of Widget* type, because an Image* type could not hold a Clock* type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Widget {
	public:
		LPCWSTR name;
		int x, y;		// Screen loc of widget
};

Widget* g_Widgets;

class Image: public Widget {
	public:
		LPCWSTR filename;
		IImage* pImage;
};

class Clock: public Widget {
	public:
		LPCWSTR fontname;
};

g_Widgets = new Image[];
g_Widgets[0].filename=L"TEST";   // DOES NOT WORK - error C2039: 'filename' : is not a member of 'Widget' 


If I change the declaration to:
Image* g_Widgets;
it works, but obviously I could not include a widget of subclass Clock in the g_Widgets array.

I am probably being a dumb newb, could someone please explain to me where I am going wrong?
Try this:
1
2
3
4
5
6
7
8
// Use vector instead of array
std::vector<Widget*> g_Widgets;
// Create an image
Image* img = new Image();
// Configure
img->filename = L"TEST";
// Add to list
g_Widgets.push_back(img);


You can only access common functions declared in Widget using g_Widgets. If you need to access a widget as a specific type, you need to cast it to the correct type. However, you should avoid this if possible. Example:
1
2
3
4
5
for (int i = 0; i < g_Widgets.size(); i++) {
  if (Image* img = dynamic_cast<Image*>(g_Widgets[i])) {
    img->pImage = new IImage; // This is executed for all Image objects in the list
  }
}
Thanks for your reply.
When you say "you should avoid this if possible" - is there a better way to do this?

I was thinking that as each subclass required a different method of rendering, I would include the render function as a member of the subclass, or use multiple inherency.

I guess there is a way to iterate through all members of a certain class? I suppose I could do that instead of trying to put them in an array or vector. However, the script file that specifies which widgets to use where will have info for portrait and landscape screen orientations, so I was going to build an array of just the ones in the current orientation at loadtime rather than iterating through them all and checking which orientation each widget is in at rendertime.

Am I needlessly over-complicating things? Is there a better way?
Last edited on
> I was thinking that as each subclass required a different method of rendering, I would include the render function as a member of the subclass, or use multiple inherency.

If the Widget class doesn't provide virtual functions for all comon operations you need, you can create a derived class where you add your render function and declare it virtual. Then derive your specific classes from that class, and declare g_Widgets as a list of pointer to the common base class. Then you can call render for every object in the list, regardless if it's a "Image" or "Clock".

> Am I needlessly over-complicating things? Is there a better way?

I think this is basically the most straight forward way to do it.
Good to know that I have gone about it the right way then, I obviously learned *something* from the tutorial.
Last edited on
try
 
#include <vector> 
> #include <vector>

Yeah, it was that and not using namespace std;

Discovered it so quick I edited it out, you were obviously too quick off the mark for me!
Dammit, yet more errors :(

1>.\openClock.cpp(244) : error C2683: 'dynamic_cast' : 'Widget' is not a polymorphic type
1> .\openClock.cpp(56) : see declaration of 'Widget'
1>.\openClock.cpp(245) : error C2259: 'IImage' : cannot instantiate abstract class

I think it is because I need a virtual function in widget to make it polymorphic? Gotta sit down and write some functions I spose.
Last edited on
I got rid of the "Widget not a polymorphic type" error, but cannot get rid of the "IImage: cannot instantiate abstract class" error:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Widget {
	public:
		LPCWSTR name;
		int x, y;		// Screen loc of widget
		virtual void dummy( ) = 0; 
};

//Widget* g_Widgets;

class Image: public Widget {
	public:
		LPCWSTR filename;
		IImage* pImage;
};

class Clock: public Widget {
	public:
		LPCWSTR fontname;
};
vector<Widget*> g_Widgets;

Image* img = new Image();   // ERROR: error C2259: 'IImage' : cannot instantiate abstract class

// Configure
img->filename = L"TEST";
// Add to list
g_Widgets.push_back(img);

for (unsigned int i = 0; i < g_Widgets.size(); i++) {
  if (Image* img = dynamic_cast<Image*>(g_Widgets[i])) {
	img->pImage = new IImage; // This is executed for all Image objects in the list
	img->filename = new LPCWSTR;
  }
}


The full error is:
1>.\openClock.cpp(237) : error C2259: 'Image' : cannot instantiate abstract class
1> due to following members:
1> 'void Widget::dummy(void)' : is abstract
1> .\openClock.cpp(60) : see declaration of 'Widget::dummy'
Last edited on
I changed

virtual void dummy( ) = 0;

to

virtual int dummy() { return (0); }

and now I get:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1>.\openClock.cpp(244) : warning C4541: 'dynamic_cast' used on polymorphic type 'Widget' with /GR-; unpredictable behavior may result
1>.\openClock.cpp(245) : error C2259: 'IImage' : cannot instantiate abstract class
1>        due to following members:
1>        'HRESULT IUnknown::QueryInterface(const IID &,void **)' : is abstract
1>        c:\program files\windows ce tools\wce500\windows mobile 5.0 pocket pc sdk\include\armv4i\unknwn.h(109) : see declaration of 'IUnknown::QueryInterface'
1>        'ULONG IUnknown::AddRef(void)' : is abstract
1>        c:\program files\windows ce tools\wce500\windows mobile 5.0 pocket pc sdk\include\armv4i\unknwn.h(113) : see declaration of 'IUnknown::AddRef'
1>        'ULONG IUnknown::Release(void)' : is abstract
1>        c:\program files\windows ce tools\wce500\windows mobile 5.0 pocket pc sdk\include\armv4i\unknwn.h(115) : see declaration of 'IUnknown::Release'
1>        'HRESULT IImage::GetPhysicalDimension(SIZE *)' : is abstract
1>        C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\include\ARMV4I\imaging.h(665) : see declaration of 'IImage::GetPhysicalDimension'
1>        'HRESULT IImage::GetImageInfo(ImageInfo *)' : is abstract
1>        C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\include\ARMV4I\imaging.h(671) : see declaration of 'IImage::GetImageInfo'
1>        'HRESULT IImage::SetImageFlags(UINT)' : is abstract
1>        C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\include\ARMV4I\imaging.h(677) : see declaration of 'IImage::SetImageFlags'
1>        'HRESULT IImage::Draw(HDC,const RECT *,const RECT *)' : is abstract
1>        C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\include\ARMV4I\imaging.h(683) : see declaration of 'IImage::Draw'
1>        'HRESULT IImage::PushIntoSink(IImageSink *)' : is abstract
1>        C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\include\ARMV4I\imaging.h(691) : see declaration of 'IImage::PushIntoSink'
1>        'HRESULT IImage::GetThumbnail(UINT,UINT,IImage **)' : is abstract
Last edited on
closed account (z05DSL3A)
virtual void dummy( ) = 0;
You need to have a void dummy() function in the Clock class.

I tried virtual void dummy( ) = 0; in combinations of all the classes, but I still get the errors like "'HRESULT IUnknown::QueryInterface(const IID &,void **)' : is abstract" as noted above.

Thanks so much for all your help, this is so confusing! Can't wait until my book arrives...
closed account (z05DSL3A)
My post is out of set with yours.

In you post "I got rid of the "Widget not a polymorphic type" error, but cannot get rid of the "IImage: cannot instantiate abstract class" error:"

You had "virtual void dummy( ) = 0;" in your base class, so needed to have "void dummy( ){};" in your derived classes (Image and Clock) that was the couse of error C2259: 'Image' : cannot instantiate abstract class.

Well I went back to basics and made a c++ console project to keep things simpler. I got it working, but it required a bit of jigging stuff around.

It works though:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include "stdafx.h"
#include <iostream>
#include <vector>
using namespace std;

class Widget {
	public:
		char * name;
		int x, y;		// Screen loc of widget
		virtual int dummy() {return(0);};
};

class Image: public Widget {
	public:
		char * filename;
		virtual int dummy() {return(0);};
};

class Clock: public Widget {
	public:
		char * fontname;
		virtual int dummy() {return(0);};
};
vector<Widget*> g_Widgets;


int _tmain(int argc, _TCHAR* argv[])
{
	// Use vector instead of array
	std::vector<Widget*> g_Widgets;
	// Create an image
	Image* img = new Image();
	Clock* clk = new Clock();
	// Configure
	img->filename = "TEST FILE\n";
	clk->fontname = "TEST FONT\n";
	// Add to list
	g_Widgets.push_back(img);
	g_Widgets.push_back(clk);

	for (unsigned int i = 0; i < g_Widgets.size(); i++) {
	  if (Image* img = dynamic_cast<Image*>(g_Widgets[i])) {
		cout << img->filename << endl;
	  } else if (Clock* clk = dynamic_cast<Clock*>(g_Widgets[i])) {
		cout << clk->fontname << endl;
	  }
	}
	return 0;
}


Now to try and put that in my Windows Mobile project...
Last edited on
Well, most of it works, but the line:

 
if (Image* img = dynamic_cast<Image*>(g_Widgets[i])) {


causes an exception in gwes.exe

Strange as the same code works in a standard win32 project...
closed account (z05DSL3A)
Do you have any warning on comile?

I noticed earlier you had "warning C4541: 'dynamic_cast' used on polymorphic type 'Widget' with /GR-; unpredictable behavior may result". If you have this warning changing the compiler switch to /GR may help.

also I think proiror posts your were using IImage wrong, here is an example from the SDK.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//
//   FUNCTION:  DrawImage(HDC)
//
//   PURPOSE: Load an image from a file and draw it to the given HDC
//
//
void DrawImage(HDC hdc)
{
    IImagingFactory *pImgFactory = NULL;
    IImage *pImage = NULL;
    RECT rc = { 0, 0, 110, 88};

    // Normally you would only call CoInitialize/CoUninitialize
    // once per thread.  This sample calls CoInitialize in this
    // draw function simply to illustrate that you must call 
    // CoInitialize before calling CoCreateInstance.
    CoInitializeEx(NULL, COINIT_MULTITHREADED);

    // Create the imaging factory.
    if (SUCCEEDED(CoCreateInstance (CLSID_ImagingFactory,
                                    NULL,
                                    CLSCTX_INPROC_SERVER,
                                    IID_IImagingFactory,
                                    (void **)&pImgFactory)))
    {
        // Load the image from the JPG file.
        if (SUCCEEDED(pImgFactory->CreateImageFromFile(
                        TEXT("\\Program Files\\Imaging\\flower.jpg"),
                        &pImage)))
        {
            // Draw the image.
            pImage->Draw(hdc, &rc, NULL);
            pImage->Release();
        }

        pImgFactory->Release();
    }
    CoUninitialize();
}


You need to enable "Runtime Type Information" or "RTTI" in your project settings to use dynamic_cast.

Second, instead of adding a useless "dummy" function to make the class "polyorphic", you should create a virtual destructor:

1
2
3
4
class Widget {
public:
  virtual ~Widget() {}
};
Thanks guys, but I think I was going about the loop the wrong way - I had an epiphany on the drive home and checked it out when I got home - it all worked.

now, rather than
1
2
3
4
5
6
7
	for (unsigned int i = 0; i < g_Widgets.size(); i++) {
	  if (Image* img = dynamic_cast<Image*>(g_Widgets[i])) {
		cout << img->filename << endl;
	  } else if (Clock* clk = dynamic_cast<Clock*>(g_Widgets[i])) {
		cout << clk->fontname << endl;
	  }
	}


I am doing something like:
1
2
3
for (int i = 0; i < g_Widgets.size(); i++) {
	g_Widgets[i]->saySomething();
}


and my classes now look more like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Widget {
	public:
		LPCWSTR name;
		int x, y;		// Screen loc of widget
		virtual void saySomething (){};
};

class Image: public Widget {
	public:
		LPCWSTR filename;
		IImage* pImage;
		void saySomething (void){msg(L"IMG");};
};

class Clock: public Widget {
	public:
		LPCWSTR fontname;
		void saySomething (void){msg(L"CLK");};
};
Last edited on
Yes, that what I had in mind when I wrote "If the Widget class doesn't provide virtual functions for all comon operations you need, you can create a derived class where you add your render function and declare it virtual."

I didn't know you were able to change the Widget class. That why I suggested to create a derived class where you could add virtual functions.

Also, the way you changed the code so that you don't need dynamic_cast was what I had in mind with "However, you should avoid this if possible." Great work!
Last edited on
Well thanks for the attempt at a morale boost, but I am still in the doldrums:(

I can get the classes set up how they should be, but using the IImagingFactory API with classes is an absolute nightmare...


I have:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Widget {					// Base Widget Class
	public:
		LPCWSTR name;	// Name of the widget - should be unique
		int x, y;		// Screen loc of widget - make RECT?
		int horizres;		// Width of screen this is for, used for orientation awareness

		virtual void saySomething (){msg(L"BASE");};
		virtual int render(HDC){return (0);};
		virtual int init(void){return (0);};
		virtual int setFilename(LPCWSTR){return(0);};
};

class Image: public Widget {	// Image Class, derived from Widget
	public:
		void saySomething (void){msg(L"IMG");};
		static IImagingFactory* pImageFactory;
		IImage * pImage;		// holds the image - loaded on call of init()
		int setFilename(LPCWSTR);
		int render (HDC);	// renders the image
		LPCWSTR filename;	// holds the filename - populated on creation
		int init (void);	// init the widget (load the image)
};

int Image::init (void) {	// Init Image widget (load image)
	if(pImage) {
		pImage->Release();
	}
	if( SUCCEEDED(g_pImageFactory->CreateImageFromFile( filename, &pImage )) ){
		return true;
	} else {
		return false;
	}
	//return true;
};

int Image::setFilename(LPCWSTR src){
	this->filename = src;
	return (1);
}


int Image::render (HDC widgHDC) {	// Render an Image widget

	// Release old image if needs be
	// Draw Image
	RECT coords;
	coords = ImgCoords (pImage, 0, 0);
	pImage->Draw( widgHDC, &coords, NULL );
	return true;
};


But I just get a blank screen - no crash, no nothing :(
Pages: 12