Search code examples
c++fltk

FLTK C++ Fl_line don't draw


I was tasked to debug a code that was meant to draw a simple polygon out of 4 points using FLTK. The MyWindow class derive from Fl_Window. The Shape class is the parent class for ClosedPolyline. Both MyWindow and Shape hold a vector to draw all of the shapes.

The problem is that after compiling and run, win.show() opens an empty window without any drawing. I'm puzzled to understand this behavior.

Here is the code (I've omitted some of the parts that are not related to drawing ClosedPolyline):

#include <iostream>

#include <FL/Fl.H>
#include <FL/Fl_Draw.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Widget.H>
#include <FL/Fl_Device.H>
#include <initializer_list>
#include <vector>
#include <functional>
//#include <cmath>
//#include <math.h>

struct Point {
int x,y;
Point(int xx, int yy) : x(xx), y(yy) { }
};

class Shape{
public:

Point point(int idx) const {
return (points[idx]);
}
unsigned int points_size() const {
return points.size();}

void draw() /*const*/{
draw_lines();
}

void add(Point p){ points.push_back(p); }

protected:
virtual void draw_lines() {}

private:
std::vector<Point> points;
};

class ClosedPolyline: public Shape {
public:
/*ClosedPolyline(std::initializer_list<Point> pp) {
if (pp.size() > 0) {
for (Point p: pp)
add(p);
}
}
*/
ClosedPolyline(Point a1, Point a2, Point a3, Point a4){
    add(a1); add(a2); add(a3); add(a4);
}
protected:
void draw_lines() override{
for (unsigned int i=1; i<points_size(); ++i){
fl_line(point(i-1).x, point(i-1).y, point(i).x, point(i).y);
}
}
};

class MyWindow: public Fl_Window {
public:
MyWindow(int x, int y, int w, int h, const char* title = 0)
: Fl_Window(x, y, w, h, title) {}
void Attach(Shape s) {
shapes.push_back(&s);
}
//void draw_shapes(){draw();}
protected:
void draw() override{
for(Shape * s: shapes) {
s->draw();
//s.draw();
}
}

private:
std::vector<Shape*> shapes;
};

And here is the main() function:

int main() {
MyWindow win(100, 100, 600, 400, "C++ Test task");

ClosedPolyline p{Point{100, 100}, Point{100, 200}, Point{500, 100}, Point{500, 200}};
win.Attach(p);
win.end();
win.show();
return (Fl::run());
}

Solution

  • Lets take a look at your MyWindow::Attach function:

    void Attach(Shape s) {
    shapes.push_back(&s);
    }
    

    In the function, the argument s is passed by value. That means it's the same as a local variable inside the function. And as such it will go out of scope and be destructed once the function return.

    Saving a pointer to that variable will lead to you saving a stray pointer, pointing to a non-existing object. Dereferencing that pointer will lead to undefined behavior, turning your whole program ill-formed and invalid.

    One way to solve the problem is to make sure that the object don't go out of scope. This can be done by using smart pointers like e.g. std::unique_ptr. And to use it from the beginning already when you define the variable p in the main function.


    Another way to solve your problem is to assume that the Shape passed to Attach will have a lifetime that outlives the Shape object, and you could therefore pass the Shape by reference:

    void Attach(Shape& s) {
        shapes.push_back(&s);
    }
    

    Now you no longer get a copy of the Shape object, and push a pointer to the original object (in your case the object p in the main function). Dereferencing the pointer will be valid as long as the original object is alive and in scope.