Jump to content

C++: How would you solve this problem?

- - - - -

This topic has been archived. This means that you cannot reply to this topic.
27 replies to this topic

#1
Lance

Lance

    Programming Professional

  • Members
  • PipPipPipPipPip
  • 276 posts
Refer to RobotGymnast's original post: Subclasses & virtual functions

Let's say we have a class hierachy:
class DrawingObject{
protected:
    std::string name;
    int x1, y1;
    int x2, y2;

public:
   DrawingObject();
   DrawingObject(const std::string& name, int x1, int y1, int x2, int y2);
   DrawingObject(const DrawingObject& obj);

   DrawingObject& operator=(const DrawingObject& rhs);

   virutal ~DrawingObject();
   
   virtual void Draw()const;
};

void DrawingObject::Draw()const
{
     std::cout<<"I am a Base object named "<<name
                  <<", I don't konw how to draw myself!" <<std::endl;
}

class Rectangle : public DrawingObject
{
public:
    Rectangle(const std::string& name, int x1, int y1, int x2, int y2)
              : DrawingObject(name, x1, y1, x2, y2);

    virtual void Draw()const;
};

void Rectangle::Draw()const
{
     std::cout<<"I am a Rectangle object named "<<name
                  <<", topLeft("<<x1<<", "<<y1<<"); bottomRight ("
                  <<x2<<", "<<y2<<std::endl;
}

class Line : public DrawingObject
{
public:
    Line(const std::string& name, int x1, int y1, int x2, int y2)
              : DrawingObject(name, x1, y1, x2, y2);

    virtual void Draw()const;
};

void Line::Draw()const
{
     std::cout<<"I am a Line object named "<<name
                  <<", from("<<x1<<", "<<y1<<"); to ("
                  <<x2<<", "<<y2<<std::endl;
}


Above code is just to demonstrate the output format, feel free to redesign the class hierachy or add new members (eg, flag), as long as you keep the 3 classes and implement the Draw() method.

Then 100 objects (of 1 of the 3 classes) will be generated randomly and be push_back'ed (the actual object instead of a pointer to it will be stored) into a std::vector<DrawingObject> container. Finally, iterate through the objects and invoke Draw() on each of them.


Please put all your code together (do not use a separate header file) and paste it within a code tags so that it can be tested easily. Compile and run you program before posting.

Thanks, Lance

#2
Lance

Lance

    Programming Professional

  • Members
  • PipPipPipPipPip
  • 276 posts
You may change any part of the classes except the class name, and the Draw() method.

#3
julmuri

julmuri

    Programmer

  • Members
  • PipPipPipPip
  • 139 posts
My first post here so, hello guys :p
I would code it like this, its not exactly what you asked but I think its easier then flag system


class IDraw

{

public:

	inline IDraw( void )

	: name_( "" )

	, width_( 0 )

	, heigth_( 0 )

	, left_( 0 )

	, top_( 0 )

	{

	}


	inline IDraw( const std::string& name, int width, int heigth, int left, int top )

	: name_( name )

	, width_( width )

	, heigth_( heigth )

	, left_( left )

	, top_( top )

	{

	}

	

	inline IDraw( const IDraw& other )

	: name_( other.name_ )

	, width_( other.width_ )

	, heigth_( other.heigth_ )

	, left_( other.left_ )

	, top_( other.top_ )

	{

	}


	inline IDraw& operator=( const IDraw& other ) {

		if ( this != &other ) {

			name_ = other.name_;

			width_ = other.width_;

			heigth_ = other.heigth_;

			left_ = other.left_;

			top_ = other.top_;

		} // if


		return *this;

	}


	inline virtual ~IDraw( void )

	{

	}

	

	virtual void draw( void ) const = 0;


protected:

	std::string name_;

	int			width_;

	int			heigth_;

	int			left_;

	int			top_;


};


class Rectangle

: public IDraw

{

public:

	inline Rectangle( void )

	: IDraw()

	{

	}


	inline Rectangle( const std::string& name, int width, int heigth, int left, int top )

	: IDraw( name, width, heigth, left, top )

	{

	}


	inline Rectangle( const Rectangle& other )

	: IDraw( other )

	{

	}


	inline Rectangle& operator=( const Rectangle& other ) {

		if ( this != &other ) {

			IDraw::operator=( other );

		}


		return *this;

	}


	inline virtual ~Rectangle( void )

	{

	}


	inline virtual void draw( void ) const

	{

		std::cout <<"I am a Rectangle object named: " << name_

				  << ", topLeft(" << top_ << ", " << left_ << "); bottomRight ("

				  << top_ + heigth_ << ", " << left_ + width_ << ")" << std::endl;

	}


};


class Line

: public IDraw

{

public:

	inline Line( void )

	: IDraw()

	{

	}


	inline Line( const std::string& name, int width, int heigth, int left, int top )

	: IDraw( name, width, heigth, left, top )

	{

	}


	inline Line( const Line& other )

	: IDraw( other )

	{

	}


	inline Line& operator=( const Line& other ) {

		if ( this != &other ) {

			IDraw::operator=( other );

		}


		return *this;

	}


	inline virtual ~Line( void )

	{

	}



	inline virtual void draw( void ) const

	{

		std::cout <<"I am a Line object named: " << name_

				  << ", topLeft(" << top_ << ", " << left_ << "); bottomRight ("

				  << top_ + heigth_ << ", " << left_ + width_ << ")" << std::endl;

	}


};


// ...


int __cdecl main( int argc, char** argv ) {

	int			     result;

	std::vector<IDraw*> vec;


	try {

		result = 0;

		vec.push_back(new Rectangle( "rect1", 1, 1, 1, 1 ));

		vec.push_back(new Rectangle( "rect2", 2, 2, 2, 2 ));

		vec.push_back(new Line( "line1", 3, 3, 3, 3 ));


		for ( std::vector<IDraw*>::iterator iter = vec.begin();

			iter != vec.end(); ++iter ) { (*iter)->draw(); }


		for ( std::vector<IDraw*>::iterator iter = vec.begin();

			iter != vec.end(); ++iter ) { delete (*iter); }

		vec.clear();

	} // try

	catch ( ... ) {

		for ( std::vector<IDraw*>::iterator iter = vec.begin();

			iter != vec.end(); ++iter ) { delete (*iter); }

		vec.clear();

		result = 1;

	} // catch


	return result;

} // main


Making draw method abstract makes sense because your base class dont know how to draw itself.
It also solves the problem with calling the appropriate draw method.
However with abstract base class you cant have vector like std::vector<IDraw> because you cant instantiate it, so you need vector of IDraw pointers.
This will be faster though cause pushing back pointer wont cause copy construction. :p
Hope this helps.

#4
wiwbiz

wiwbiz

    Learning Programmer

  • Members
  • PipPipPip
  • 67 posts
It's a simple problem ,but not working ..can you tell where the error is ?
#include<iostream>
using namespace std;
int main()
{
    int a,b=0;
    cout<<"Enter the no. to find the sum of series";
    cin>>a;
    do{
        a=a+b;
        b++;
    }while(b<=a);
    cout<<a;
    cin.get();
    cin.ignore();
    return 0;
}        
I need to input 4 as a and get out put as "4"+0+1+2+3+4=14

Also when exactly do we need to use both cin.get() and cin.ignore() as sometimes cin.get() only has worked for me fine, but sometimes the wiindow just dissapears without showing the output.Thanks

Edited by WingedPanther, 23 December 2008 - 08:42 AM.
add code tags (the # button)


#5
Lance

Lance

    Programming Professional

  • Members
  • PipPipPipPipPip
  • 276 posts

julmuri said:

My first post here so, hello guys :p
I would code it like this, its not exactly what you asked but I think its easier then flag system


class IDraw

{

public:

	inline IDraw( void )

	: name_( "" )

	, width_( 0 )

	, heigth_( 0 )

	, left_( 0 )

	, top_( 0 )

	{

	}


	inline IDraw( const std::string& name, int width, int heigth, int left, int top )

	: name_( name )

	, width_( width )

	, heigth_( heigth )

	, left_( left )

	, top_( top )

	{

	}

	

	inline IDraw( const IDraw& other )

	: name_( other.name_ )

	, width_( other.width_ )

	, heigth_( other.heigth_ )

	, left_( other.left_ )

	, top_( other.top_ )

	{

	}


	inline IDraw& operator=( const IDraw& other ) {

		if ( this != &other ) {

			name_ = other.name_;

			width_ = other.width_;

			heigth_ = other.heigth_;

			left_ = other.left_;

			top_ = other.top_;

		} // if


		return *this;

	}


	inline virtual ~IDraw( void )

	{

	}

	

	virtual void draw( void ) const = 0;


protected:

	std::string name_;

	int			width_;

	int			heigth_;

	int			left_;

	int			top_;


};


class Rectangle

: public IDraw

{

public:

	inline Rectangle( void )

	: IDraw()

	{

	}


	inline Rectangle( const std::string& name, int width, int heigth, int left, int top )

	: IDraw( name, width, heigth, left, top )

	{

	}


	inline Rectangle( const Rectangle& other )

	: IDraw( other )

	{

	}


	inline Rectangle& operator=( const Rectangle& other ) {

		if ( this != &other ) {

			IDraw::operator=( other );

		}


		return *this;

	}


	inline virtual ~Rectangle( void )

	{

	}


	inline virtual void draw( void ) const

	{

		std::cout <<"I am a Rectangle object named: " << name_

				  << ", topLeft(" << top_ << ", " << left_ << "); bottomRight ("

				  << top_ + heigth_ << ", " << left_ + width_ << ")" << std::endl;

	}


};


class Line

: public IDraw

{

public:

	inline Line( void )

	: IDraw()

	{

	}


	inline Line( const std::string& name, int width, int heigth, int left, int top )

	: IDraw( name, width, heigth, left, top )

	{

	}


	inline Line( const Line& other )

	: IDraw( other )

	{

	}


	inline Line& operator=( const Line& other ) {

		if ( this != &other ) {

			IDraw::operator=( other );

		}


		return *this;

	}


	inline virtual ~Line( void )

	{

	}



	inline virtual void draw( void ) const

	{

		std::cout <<"I am a Line object named: " << name_

				  << ", topLeft(" << top_ << ", " << left_ << "); bottomRight ("

				  << top_ + heigth_ << ", " << left_ + width_ << ")" << std::endl;

	}


};


// ...


int __cdecl main( int argc, char** argv ) {

	int			     result;

	std::vector<IDraw*> vec;


	try {

		result = 0;

		vec.push_back(new Rectangle( "rect1", 1, 1, 1, 1 ));

		vec.push_back(new Rectangle( "rect2", 2, 2, 2, 2 ));

		vec.push_back(new Line( "line1", 3, 3, 3, 3 ));


		for ( std::vector<IDraw*>::iterator iter = vec.begin();

			iter != vec.end(); ++iter ) { (*iter)->draw(); }


		for ( std::vector<IDraw*>::iterator iter = vec.begin();

			iter != vec.end(); ++iter ) { delete (*iter); }

		vec.clear();

	} // try

	catch ( ... ) {

		for ( std::vector<IDraw*>::iterator iter = vec.begin();

			iter != vec.end(); ++iter ) { delete (*iter); }

		vec.clear();

		result = 1;

	} // catch


	return result;

} // main


Making draw method abstract makes sense because your base class dont know how to draw itself.
It also solves the problem with calling the appropriate draw method.
However with abstract base class you cant have vector like std::vector<IDraw> because you cant instantiate it, so you need vector of IDraw pointers.
This will be faster though cause pushing back pointer wont cause copy construction. :p
Hope this helps.


Beatiful code. I like your coding style and obviously you know the language quite well.

Even though what I asked is to store objects instead of pointers to objects in the vector, I know you should have no problem solve it elegantly. So you may try and just let me know you "did it" without actually post the code.

Thanks Julmuri, nice try!

#6
Lance

Lance

    Programming Professional

  • Members
  • PipPipPipPipPip
  • 276 posts
Making draw method abstract makes sense because your base class dont know how to draw itself.
It also solves the problem with calling the appropriate draw method.
However with abstract base class you cant have vector like std::vector<IDraw> because you cant instantiate it, so you need vector of IDraw pointers.
This will be faster though cause pushing back pointer wont cause copy construction.
Hope this helps.

Agreed but that's not the point I was trying to make here. In certain situation, it may be desirable to store objects instead of pointers, for example, when the objects are of the same or substantially the same size across the class hierachy and the objects are so small that a pointer to it can be a big overhead (proportionately).

#7
Lance

Lance

    Programming Professional

  • Members
  • PipPipPipPipPip
  • 276 posts
#include<iostream>
using namespace std;
int main()
{
    int a,b=0;
    cout<<"Enter the no. to find the sum of series";
    cin>>a;
    do{
        a=a+b;
        b++;
    }while(b<=a);
    cout<<a;
    cin.get();
    cin.ignore();
    return 0;
}

Your problem lies in the fact that you used a for 2 different purposes: first the sum of the series, and second the loop boundary. The two role are conflicting with each other. So if you introduce another variable, sum, to store the sum, you should have no problem fixing it.

In this particular case, I don't see the need of using do{}while; loop. How about a simple for loop?

...
unsigned sum=0;
for(int b=0; b<a; ++b)
     sum += b;
...

Note, a=a+b is a valid expression and most likely a compiler will optimize it to generate the same machine code as a += b. However, being a c/C++ programmer, you are recommended to use a += b. And they are not always equivalent to each other, eg,, when a and b are of complicated user-defined data types.

#8
Lance

Lance

    Programming Professional

  • Members
  • PipPipPipPipPip
  • 276 posts
Flag and Switch, a typical C approach:


#include <string>

#include <vector>

#include <iostream>

#include <time.h>

#include <stdlib.h>

#include <stdio.h>


void test();


int main()

{

	test();

}


 


// use a flag to determine how to draw a object. It's still C++ code, but we are

// approaching the problem with a typical C way.

//


enum {DOObject, RectObject, LineObject};


class DrawingObject

{

protected:

	std::string name;

	int x1, y1, x2, y2;

    int type;


public:

	DrawingObject():type(DOObject){}

	DrawingObject(const std::string& name,

		int x1, int y1,	

		int x2, int y2,

		int type=DOObject   )

			:name(name), x1(x1), y1(y1), x2(x2), y2(y2), type(type)

	{

	}


	DrawingObject(const DrawingObject& o)

		: name(o.name), x1(o.x1), y1(o.y1), x2(o.x2), y2(o.y2), type(o.type)

	{

	}


 


	void Draw()const

	{

        switch(type)

        {

        case DOObject:

			std::cout<<"I am a DrawingObject object named "<<name

                <<", I don't know how to draw myself!"<<std::endl;

            break;


        case RectObject:

        	std::cout<<"I am a Rectangle object named "<<name

			    <<", topLeft ("<<x1<<", "<<y1    <<"); bottomRight ("

                <<x2<<", "<<y2<<")."<<std::endl;

            break;


        case LineObject:

        	std::cout<<"I am a Line object named "<<name<<", from ("

                <<x1<<", "<<y1<<"); to ("<<x2<<", "<<y2<<")."<<std::endl;

            break;


        default:

            std::cout<<"Invalid object!"<<std::endl;


        }

	}


};



class Rectangle: public DrawingObject

{

public:

	Rectangle(const std::string& name, int x1, int y1, int x2, int y2):

		DrawingObject(name, x1, y1, x2, y2, RectObject)

	{

	}


};


class Line: public DrawingObject

{

public:

	Line(const std::string& name, int x1, int y1, int x2, int y2):

		DrawingObject(name, x1, y1, x2, y2, LineObject)

	{

	}

};


 


void test()

{

	std::vector<DrawingObject> objects;


	int type, x1, x2, y1, y2;

	char name[1024];


	static const char * prefix[]={"draw","rect", "line"};



	srand( (unsigned)time(NULL) );


	for(unsigned i=0; i<100; ++i){

	        type=rand()%3;

	        x1=rand()%800;

	        y1=rand()%800;

	        x2=rand()%800;

	        y2=rand()%800;


	        snprintf(name, 1023, "%s_%d", prefix[type], i);


	        switch(type){

	        case DOObject:

	                    objects.push_back(DrawingObject(name, x1, y1, x2, y2));

	                    break;


	        case RectObject:

	                    objects.push_back (Rectangle (name, x1, y1, x2, y2) );

	                    break;


	        case LineObject: // default

	                    objects.push_back(Line(name, x1, y1, x2, y2));

	                    break;


	        }


	}


	for(unsigned i=0; i<objects.size(); ++i)

		objects[i].Draw();


}


Plain and straightforward. Work as intended. Fast and efficient

#9
Lance

Lance

    Programming Professional

  • Members
  • PipPipPipPipPip
  • 276 posts
This code doesn't behave as expected, do you know what's wrong and how to fix it?

#include <string>
#include <vector>
#include <iostream>
 

#include <time.h>
#include <stdlib.h>
#include <new>

 

void test();

 

int main()
{
            test();
}
   

class DrawingObject
{
protected:

            std::string name;
            int x1, y1, x2, y2;
 

public:
            DrawingObject(){}
            DrawingObject(const std::string& name, int x1, int y1, int x2, int y2)
                        :name(name), x1(x1), y1(y1), x2(x2), y2(y2)

            {
            }

            DrawingObject(const DrawingObject& o)
                        : name(o.name), x1(o.x1), y1(o.y1), x2(o.x2), y2(o.y2)
            {
            }

            virtual void Draw()const
            {
                        std::cout<<"I am a DrawingObject object named "<<name<<", I don't know how to draw myself!"<<std::endl;
            }
};

class Rectangle: public DrawingObject
{
public:
            Rectangle(const std::string& name, int x1, int y1, int x2, int y2):
                        DrawingObject(name, x1, y1, x2, y2)
            {
            }

            virtual void Draw()const
            {
				std::cout<<"I am a Rectangle object named "<<name<<", topLeft ("<<x1<<", "<<y1
                        <<"); bottomRight ("<<x2<<", "<<y2<<")."<<std::endl;
            }

};


class Line: public DrawingObject
{
public:
            Line(const std::string& name, int x1, int y1, int x2, int y2):
                        DrawingObject(name, x1, y1, x2, y2)
            {
            }

            virtual void Draw()const
            {
                       std::cout<<"I am a Line object named "<<name<<", from ("<<x1<<", "<<y1
                                   <<"); to ("<<x2<<", "<<y2<<")."<<std::endl;
            }

};

void test()
{
            std::vector<DrawingObject> objects;
            int type, x1, x2, y1, y2;
            char name[1024];
            static const char * prefix[]={"draw","rect", "line"};
            
            srand( (unsigned)time(NULL) );
            for(unsigned i=0; i<100; ++i){
                        type=rand()%3;
                        x1=rand()%800;
                        y1=rand()%800;
                        x2=rand()%800;
                        y2=rand()%800;
                        snprintf(name, 1023, "%s_%d", prefix[type], i);
                        switch(type){
                        case 0:            
                                    objects.push_back(DrawingObject(name, x1, y1, x2, y2));
                                    break;

                        case 1:
                                    objects.push_back(Rectangle(name, x1, y1, x2, y2));
                                    break;

                        case 2: // default
                                    objects.push_back(Line(name, x1, y1, x2, y2));
                                    break;
                        }

            }
            for(unsigned i=0; i<objects.size(); ++i)
                        objects[i].Draw();

}


#10
Lance

Lance

    Programming Professional

  • Members
  • PipPipPipPipPip
  • 276 posts
The ugliest way, introduce a type indicator and keep Draw as virtual. When accessing the objects



          for(int i=0; i<objects.size(); ++i){

                switch(objects[i].type){

                case 0: /* DrawObject */

                         objects[0].Draw();

                         break;

                case 1: /* Rectangle */

                       {

                           Rectangle r(reinterpret_cast<const Rectangle&>(objects[i]));

                           r.Daw();

                       }

                      break;

                case 2: /* Line */

                       {

                           Line l(reinterpret_cast<const Rectangle&>(objects[i]));

                           l.Daw();

                       }

                      break;

          }


...


It sure generates corrent result (copy constructor need to be implemented for Rectangle and Line) but I would not say it works fine.

#11
Lance

Lance

    Programming Professional

  • Members
  • PipPipPipPipPip
  • 276 posts
Solution 1.

We will start from where the program in the 9th reply fails.

It failed because of
DrawingObject::DrawingObject(const DrawingObject& rhs);
The code correctly copied every member of rhs, but it will not copy the vptr (which determines when a virtual function, such as Draw(), is invoked from a pointer or reference, which funtion is actually called). Instead, the compiler consider the vptr as its exclusive domain. It gurantee the vptr is initialize to point to DrawingObject virtual table. This is exactly why the program in 9th reply lose the polymorphism a less informed C++ programmer may expect.

As we understand where the problem stems from, we can fix it accordingly. C++ doesn't allow explicit access to vptr. However, if we know its whereabout within the object, there is no reason we cannot change it, as long as we know what we are doing. Now the only problem is where is the vptr.

C++ standard doesn't say where a vendor is expected to put the vptr, some choose to put in the beginning of a object, others like to locate it at the end of a object (let's ignore multiple vptr for know). But for empty class/object, the vptr is always the same as beginning now equals end. So we have our first C++ solution.


struct IDrawable{
       void Draw()const=0;
};

class DrawingObject: public IDrawable{
public:
     ...
     DrawingObject(const DrawingObject& rhs)
                  : name(rhs.name), x1(rhs.x1), y1(rhs.y1),
                    x2(rhs.x2), y2(rhs.y2)
      {
               /* now in user code domain, the compiler generated code should have
                *    finished initializing vptr to DrawingObject vtable. In our case
                *  this default behaviour is not always what we would like it to be.
                *  This could be the best chance to correct it.
                *  
                *  the following line set vptr to rhs.vptr, we assume sizeof(int)=sizeof(vptr),
                *  which should be a safe assumption
                */
                  *reinterpret_cast<int*>(this) = *reinterpret_cast<const int *>(&rhs);
      }

....all other code remains the same


#12
Lance

Lance

    Programming Professional

  • Members
  • PipPipPipPipPip
  • 276 posts
Merry Christmas and happy new year to all CodeCallers. I will come up with a possibly even better solution, after the new year. Stay tuned.