【问题标题】:How can I simulate a nested function without lambda expressions in C++11?如何在 C++11 中模拟没有 lambda 表达式的嵌套函数?
【发布时间】:2015-06-16 04:19:59
【问题描述】:

我有以下代码:

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;
}

我收到以下错误:

||=== 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 用于Allegro 5 library,我已经将主循环的绘图部分(以及事件部分)抽象为带有绘图(函数)的“场景”对象。每个过程都被传递给应用程序,以便它在主循环中“运行”。问题是,“C++ 方法”不起作用:

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;
}

虽然代码运行了,但还是会发生这种情况:

如果我把 x 放在 visual_plot 里面,图片就会正常加载:

但是现在我遇到了一个巨大的性能问题,因为在每个主循环中都会创建一个新的 Image 对象(而且不久之后整个事情就会冻结)。

当我把它放在函数范围之外时找不到图像,因为 它必须 在应用程序初始化之后出现,但是因为我在 Scene 中有一个 typedef 函数指针,它采用该函数作为一个论点,我也必须给它一个 void 函数。问题是我无法在 C++ 中创建本地/嵌套函数(在应用程序初始化之后)。所以,为了避免这个问题,我尝试了明显的(Lambda表达式/闭包):

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;
}

问题是Scene的构造函数的第二个参数带了一个函数指针:

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);
};

由于函数不能作为参数传递,并且会衰减为指针,因此同样适用于 lambda 表达式(它不是函数),所以我收到以下错误:

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)'|

面对这样的悲剧,如何在C++11中模拟嵌套函数? Since simulating like this answer 不起作用。

OBS:我同意这可能是一个设计问题,但我几乎不这么认为。对我来说,C++ 只是不希望我以任何方式将那个该死的函数作为参数传递(所以,我请求各位 C++ 专家的帮助)。

【问题讨论】:

  • Image x("sample.png"); 移动到visual_plot 中并使其变为static?或者,将std::function&lt;void()&gt; 用作plot,而不是函数指针。
  • 我不能把Image x("sample.png");放在visual_plot过程中,因为这会冻结应用程序(Since visual_plot is being run at each_frame in a main_loop
  • "并使其成为static"(静态局部只初始化一次,第一次调用该函数)。
  • @Praetorian OP 确实说 “当我把它放在函数范围之外时找不到图像,因为它必须在应用程序初始化之后出现” 虽然我你在文字和图片墙上错过了它并不奇怪:)
  • 如果您有大量图像,请将它们全部放入您在全局 unique_ptr 中保存的结构中,并在创建 App 后将它们全部初始化。然后进行初始化可能会更好,否则您会有一种“即时”初始化,当您不准备处理它时可能会失败。

标签: c++ function c++11 lambda allegro


【解决方案1】:

只需将图像放在visual_plot 函数中并使其成为静态:

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

这将在第一次调用visual_plot 时初始化img,并且仅在那时。这将解决性能问题和“必须在app.run() 之后初始化”问题。

【讨论】:

  • “使其成为静态”部分实际上就是所需要的。是否在visual_plot 函数内部并不重要。 OP 的原始尝试 Image x("sample.png"); struct foo { static void visual_plot() { x.draw(); } }; // Error. 也将通过制作 x static 来修复。
【解决方案2】:

这是一个设计问题。为了完成你想要做的事情,你需要两条信息:要执行的代码和要执行的数据。

lambda 并不神奇,它只是将这两者封装到一个对象中,这就是为什么它不会很好地衰减为单个函数指针。带有捕获的 lambda 是函数对象的语法糖:

int x, y;
float f;

// ...

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

让你免于写作

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();

最后是一个成员函数调用,因此涉及两个指针:一个指向成员函数“operator()”的指针和一个指向调用它的实例的指针。

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

使用静态或全局变量,您可以在编译时知道数据的地址,因此您只需要一个函数指针。

但是你的设计是一个只接受一个参数的 strcpy 或一个不接受任何参数的打印函数:你怎么知道要复制或打印什么?

更好的设计是让您将函数对象传递给绘图函数,看看 STL 谓词如何工作,这将允许函数指针和 lambda,或者使用虚函数和子类化。

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

这种方法的缺陷是“slicing”,如果您允许派生类型,则需要通过引用而不是值传递Scene

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

foo(MyScene(...)); // not going to go well

【讨论】:

    猜你喜欢
    • 2011-07-18
    • 1970-01-01
    • 1970-01-01
    • 2013-12-18
    • 1970-01-01
    • 2016-08-23
    • 1970-01-01
    • 1970-01-01
    • 2020-05-01
    相关资源
    最近更新 更多