2

I have the following code:

int main(int argc, char **argv)
{
    App app(800, 600);
    app.add_event_scene(Scene("Event Plot", event_plot));

    Image x("sample.png");
    struct foo { static void visual_plot() { x.draw(); } }; // Error.

    app.add_visual_scene(Scene("Visual Plot", foo::visual_plot));
    app.run();
    return 0;
}

And I get the following error:

||=== Build: Debug in Joy-Plus-Plus (compiler: GNU GCC Compiler) ===|
G:\Development\Game-Development\CB\Joy-Plus-Plus\main.cpp|54|error: use of local variable with automatic storage from containing function|
G:\Development\Game-Development\CB\Joy-Plus-Plus\main.cpp|53|error:   'Image x' declared here|
||=== Build failed: 2 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

I'm writing a multimedia/game engine for the Allegro 5 library, and I've abstracted the drawing part of the main-loop (As well as the event parts) into "scene" objects with plots (Functions). Each procedure is passed to the App, so that it gets "run" inside the main-loop. The problem is, the "C++ approach" does not work:

Image x("sample.png");

void visual_plot()
{
    x.draw(); // Problem.
}

int main(int argc, char **argv)
{
    App app(800, 600);
    app.add_event_scene(Scene("Event Plot", event_plot));
    app.add_visual_scene(Scene("Visual Plot", visual_plot));
    app.run();
    return 0;
}

Although the code runs, this happens:

enter image description here

And if I put the x inside the visual_plot, the image is loaded normally:

enter image description here

But now I have a huge performance problem, since a new Image object is being created at each main-loop (And it's not long until the whole thing freezes).

The image is not found when I put it outside the scope of the function because it must come after the initialization of the App, but since I have a typedef function pointer in Scene that takes that function as an argument, I also must give it a void function. The problem is that I can't create local / nested functions in C++ (After the initialization of the App). So, in order to avoid the problem, I've tried the obvious (Lambda expression / closure):

int main(int argc, char **argv)
{
    App app(800, 600);
    app.add_event_scene(Scene("Event Plot", event_plot));
    Image x("sample.png");
    app.add_visual_scene(Scene("Visual Plot", [&x]()->void{x.draw();}));
    app.run();
    return 0;
}

The problem is that the second argument of the constructor of Scene takes a function pointer:

typedef void(*plot)();
typedef map<string, plot> act;

class Scene
{
private:
    string name;
    plot p;
public:
    Scene(string name, plot p);
    ~Scene() {};
    string get_name();
    plot get_plot();
    void set_name(string value);
    void set_plot(plot value);
};

And since functions cannot be passed as parameters, and get decayed to pointers, the same also applies to the lambda expression (Which is not a function), so I get the following error:

G:\Development\Game-Development\CB\Joy-Plus-Plus\main.cpp|52|error: no matching function for call to 'Scene::Scene(const char [12], main(int, char**)::__lambda0)'|

Facing such a tragedy, how can I simulate a nested function in C++11? Since simulating like this answer does not work.

OBS: I agree that it could be a design problem, but I pretty much don't see it that way. For me, C++ just don't want me to pass that bloody function as a parameter by any means (So, I ask for the help of you long C++ Wizards).

Community
  • 1
  • 1
Ericson Willians
  • 7,606
  • 11
  • 63
  • 114
  • 1
    Move `Image x("sample.png");` inside `visual_plot` and make it `static`? Alternatively, use `std::function` as `plot` instead of a function pointer. – T.C. Jun 16 '15 at 04:21
  • I can't put `Image x("sample.png");`inside the visual_plot procedure because that will freeze the application ([Since visual_plot is being run at each_frame in a main_loop](https://github.com/EricsonWillians/Joy-Plus-Plus/blob/master/app.cpp#L138)) – Ericson Willians Jun 16 '15 at 04:24
  • Declare `x` as a global variable and initialise it after the `App` is initialised? – Jonathan Potter Jun 16 '15 at 04:25
  • 1
    "and make it `static`" (a static local is initialized only once, the first time the function is called). – T.C. Jun 16 '15 at 04:25
  • I don't understand why your second example doesn't work, unless there's a limitation to loading an `Image` until an `App` has been constructed, or something similar. If that's the case, either use a `static Image` as TC says, or declare a global `unique_ptr x;`, and in `main` initialize it as `x.reset(new Image("sample.png"));` before you `add_visual_scene` – Praetorian Jun 16 '15 at 04:30
  • 2
    @Praetorian the OP did say _"The image is not found when I put it outside the scope of the function because it must come after the initialization of the App"_ although I'm not surprised you missed it in the wall of text and images :) – Jonathan Potter Jun 16 '15 at 04:40
  • Well, I had no idea about the static local variables (I'm reading about it). Apparently that solves the issue (But I'm verifying the FPS to check it). [Is that fast/good](http://stackoverflow.com/questions/246564/what-is-the-lifetime-of-a-static-variable-in-a-c-function)? I mean, eventually, tons of images could get loaded that way, and the lifetime of the static local seems mysterious (I mean, I don't feel as safe about it, as I would if I could just use a nested function, as in Python or whatever, or if I could use a lambda with captures with a pointer / as parameter). – Ericson Willians Jun 16 '15 at 04:49
  • 1
    If you have a ton of images, put them all in a structure that you hold in a global `unique_ptr` and initialise them all after your `App` is created. It's probably better to do the initialisation then, otherwise you have a kind of "just-in-time" initialisation which might fail when you're not prepared to handle it. – Jonathan Potter Jun 16 '15 at 04:53

2 Answers2

4

Simply put the image inside the visual_plot function and make it static:

void visual_plot()
{
    static Image img("sample.png");
    x.draw(); // Problem.
}

This will initialize img the first time visual_plot is called, and only then. This will solve both the performance problem and the "it must be initialized after app.run()" issue.

Shoe
  • 74,840
  • 36
  • 166
  • 272
  • The "make it static" part is actually all that's needed. Whether it's inside the `visual_plot` function is not important. The OP's original attempt `Image x("sample.png"); struct foo { static void visual_plot() { x.draw(); } }; // Error.` would also be fixed by making `x` `static`. –  Jun 16 '15 at 07:41
1

It is a design problem. In order to accomplish what you are trying to do you need two pieces of information: the code to execute and the data to execute it against.

A lambda isn't magic, it simply encapsulates both of these into an object, that's why it doesn't decay nicely to a single function pointer. A lambda with captures is syntactic sugar for a function object:

int x, y;
float f;

// ...

auto l = [x, &y, f] () { return static_cast<int>((x + y) * f); };
int r = l();

is saving you from writing

struct Values {
    int x;
    int& y;
    float f;

    int operator() () {
        return static_cast<int>((x + y) * f);
    }

    Capture(int x_, int& y_, float f_) : x(x_), y(y_), f(f_) {}
};

//...

Capture c(x, y, f);

int r = c();

That's a member function call at the end there, so two pointers are involved: a pointer to the member function 'operator()' and a pointer to the instance to call it on.

int r = Capture::operator=(&c); // pseudo

Using a static or global variable you could make the address of the data known at compile time and so allow yourself to only need a function pointer.

But your design is that of a strcpy that only takes one argument or a print function that takes none: how do you know what to copy or print?

Better designs would be either to let you pass a function object to the plot functions, see how STL predicates work, which would allow both function pointers and lambdas, or use virtual functions and subclassing.

struct Scene { virtual void plot(); };
struct MyScene : public Scene {
    Image x;
    MyScene() : x("image") {}
    void plot() override { x.draw(); }
};

The pitfall of this approach is "slicing", you need to pass Scene by reference rather than by value if you are allowing derived types:

void foo(Scene& s) {
    s.plot();
}

foo(MyScene(...)); // not going to go well
Community
  • 1
  • 1
kfsone
  • 23,617
  • 2
  • 42
  • 74