C++ lambda 作为类方法

C++ lambdas as class methods

本文关键字:类方法 lambda C++      更新时间:2023-10-16

作为一个假设的问题,我想使用lambda作为类方法。我知道这在专业背景下很糟糕,但无论如何我都很好奇。一个例子可能最适合展示我想做什么。下面是复数的基本类:

class Complex {
private:
double re, im;
public:
Complex() : re(0.0), im(0.0) {}
Complex(double re, double im) : re(re * 1.0), im(im * 1.0) {}
Complex(const Complex &c) = default;
~Complex() = default;
function<double(void)> getRe = [=]() -> double { return re; };
function<void(double)> setRe = [&](double re) -> void { this->re = re; };
function<double(void)> getIm = [=]() -> double { return im; };
function<void(double)> setIm = [&](double im) -> void { this->im = im; };
};

起初我尝试使用auto而不是显式指定函数类型,但我收到错误,说我不能在非静态字段中使用auto

这似乎实际上有效,因为它显然产生了所需的行为。我已经使用它使用 OpenGL 绘制了一些分形,因此它最终做了一些相当密集的工作。

正如我所说,它似乎有效,我已经为setter使用了引用捕获,特别是因为我认为由于this是对当前实例的引用,因此可能需要它,并且对于getters的值捕获,因为默认情况下标识符最终在类范围内搜索(在这种情况下)并找到字段。

我有两个问题:

  1. 我还没有用Visual Studio对此进行测试,但是在我正在使用的CLion和MSVC编译器中,this被突出显示为不存在的变量(即使它"有效")。知道为什么会这样吗?

  2. 课程最终变得缓慢。就像慢了一个数量级以上一样。当我使用像double getRe() {return re;}这样的普通 getter 和 setter 时,渲染从绝对即时到需要 2-3 秒。为什么会这样?

我喜欢这个想法,但在实践中效果并不好。

std::function是一种类类型,就像您可能编写的任何其他自定义类一样。sizeof(std::function)因实现而异,但合理的值是字节124。这意味着,与为每个成员函数添加0字节相比,您将为要添加的每个成员std::functionsizeof(Complex)添加24字节。与大多数机器上的sizeof(double) == 8相比,这是很大的开销:您的Complex类型可能是16字节,但大约是112字节。

此外,每个std::function成员都必须初始化,可能需要堆分配,并且由于类型擦除,调用std::function涉及虚函数(或等效功能)。这使得编译器很难优化,并且编译器几乎不可能内联函数,而常规成员函数由于非常简单,几乎可以保证内联。

对成员函数使用std::function意味着您的类型无用地更大,初始化需要更多工作,并且更难优化。这就是为什么它的速度要慢得多。

1:此时,sizeof(std::function)实际上在libstdc++,libc++和MSVC的STL上分别是32字节,48字节和64字节


为了避免每个对象的开销,你可以有static constexpr个成员(至少在 C++17 中),但你必须有一个显式的this参数,它删除了成员函数具有的所有好处。你必须写Complex::getRe(myComplex)而不是myComplex.getRe()

让我为您修复此代码:

struct Complex {
double re, im;
Complex() : re(0.0), im(0.0) {}
Complex(double re, double im) : re(re * 1.0), im(im * 1.0) {}
Complex(const Complex &c) = default;
~Complex() = default;
};

完全相同的结果,更好的可读性,更小的内存占用。去吧。你的代码对于恒定正确性也非常糟糕。

是的,由于多种因素,您的代码非常慢:

  • 您对方法的调用现在是虚拟化的,因此它们不是内联的,您最终会因此受到严重的惩罚
  • 您的对象创建/销毁现在很可能会导致动态内存分配/解除分配
  • 您的对象没有变大,因此缓存未命中更多