Rares Dima
Rares Dima

Reputation: 1747

C++ lambdas as class methods

As a hypothetical question, I would like to use lambdas as class methods. I understand this is bad in a professional context, but I'm curious anyway. An example would probably be best for showing what I want to do. Here is a basic class for complex numbers:

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

};

At first I have tried using auto instead of explicitly specifying function types but I got errors saying that I cant use auto in non static fields.

This seems to actually work, as in it apparently produces the desired behaviour. I have used it to draw some fractals using OpenGL so it ends up doing some fairly intensive work.

As I said it seems to work, I have used reference capture for the setters especially since I figured that since this is a reference to the current instance it might be needed, and value capture for the getters since by default an identifier ends up searching(in this case) in the class scope and finds the fields.

I have 2 questions:

  1. I have not tested this with visual studio but in what I am using, CLion with the MSVC compiler, the this is highlighted as as an inexistent variable(even though it "works"). Any ideas why this happens?

  2. The class ends up being slow. As in more than an order of magnitude slower. The rendering goes from being absolutely instant when I use plain getters and setters like double getRe() {return re;}, to taking 2-3 seconds. Why does this happen?

Upvotes: 9

Views: 8411

Answers (2)

Justin
Justin

Reputation: 25277

I like the idea, but it doesn't work out so well in practice.

std::function is a class type, like any other custom class you might write. sizeof(std::function) varies from implementation to implementation, but a reasonable value is 24 bytes1. This means that you'd be adding 24 bytes to sizeof(Complex) for every member std::function you want to add, compared to adding 0 bytes for every member function. Compared to sizeof(double) == 8 on most machines, that's a lot of overhead: your Complex type could be 16 bytes but is instead roughly 112 bytes.

Furthermore, every std::function member has to be initialized, possibly requiring a heap allocation, and calling a std::function involves virtual functions (or equivalent) because of type erasure. This makes it really hard for the compiler to optimize and nearly impossible for the compiler to inline the functions, whereas the regular member functions are almost guaranteed to be inlined due to how simple they are.

Using std::function for member functions means that your type is uselessly bigger, takes more work to initialize, and is much harder to optimize. That's why it's so much slower.

1: At this time, sizeof(std::function) is actually 32 bytes, 48 bytes, and 64 bytes on libstdc++, libc++, and MSVC's STL respectively


To avoid the per-object overhead, you could have static constexpr members (at least in C++17), but then you'd have to have an explicit this parameter, which removes all the nice sugar that member functions have. You'd have to write Complex::getRe(myComplex) rather than myComplex.getRe()

Upvotes: 7

SergeyA
SergeyA

Reputation: 62553

Let me fix this code for you:

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

Exactly the same outcome, better readability, smaller memory footprint. Go for it. You code is also pretty terrible for const-correctness.

And yes, your code is so terribly slow because of multiple factors:

  • Your calling to methods is now virtualized, so they are not inlined, you end up being penalized terribly for this
  • Your object creation/destruction now is very likely to incur dynamic memory allocations/deallocations
  • Your objects have no become bigger, so more cache misses

Upvotes: -2

Related Questions