c++ 将成员变量的值从封闭空间复制到结构中

camsedfj  于 2023-08-09  发布在  其他
关注(0)|答案(1)|浏览(99)

考虑以下代码行:

template <typename B>
float cannot_change(float second) {
    B b(second);
    return b();
}

int main() {
    static float global = 10;
    struct C {
        float first_ = global;
        float second_;
        C(float second) : second_(second) {}
        float operator()() { return first_ * second_; }
    };
    std::cout << cannot_change<C>(20) << std::endl;
}

字符串
函数cannot_change构造了一个模板参数B,该参数只有一个参数。函数是固定的,不能更改,但我知道它的源代码。假设我想传递一个结构体,它接受两个成员函数(C)。我创建了一个结构体C,它使用了静态变量global
我有几个问题

  • 像我这样写结构体C是合法的吗?还是应该避免这样做?这看起来很奇怪,但我想在cannot_change中使用一个B类型的元素,并带有两个成员变量。
  • 如果这是合法的,是否有其他方法为C设置变量first_?我更喜欢复制变量。删除static关键字会导致编译错误。
bnl4lu3b

bnl4lu3b1#

看起来你正在尝试做的是最好用lambda捕获来完成的,就像这样:

#include <iostream>

int main()
{
    const float local = 10;
    auto f = [local](float second) { return local * second; };

    std::cout << f(20) << std::endl;
    return 0;
}

字符串
如果你需要处理一些类的成员,你也可以做一个lambda模板(所以你可以写const B & b,而不是float second,并在函数体中使用b.member),像这样:

#include <iostream>

int main()
{
    const float local = 10;
    struct Something {
       float member1;
       float member2;
    };
    auto f = [local]<typename B>(const B & b) { return local * b.member1 + b.member2; };

    std::cout << f(Something{3, 14}) << std::endl;
    return 0;
}


Godbolt
如果你真的需要用这个接口调用cannot_change,那么你可以像这样解决它:

#include <iostream>
#include <functional>

// You can't change this, so you can't use a lambda directly here
template <typename B>
float cannot_change(float second) {
    B b(second);
    return b();
}

// Here's the function signature of the lambda (and the functor B)
typedef float (*func_type)(float);

namespace details
{
    // Here the idea is to capture the lambda in a std::function static instance. 
    // We are not capturing the context of the lambda but the lambda instance itself
    template <typename T>
    struct CaptureLambda {
        // That's the function pointer that'll be used by the Adapter class. 
        // It's instantiated by the compiler on line 35
        static float adapter(float f) {
            // Actually call the std::function => lambda with the "second" parameter of B
            return get()(f);
        };
        // Usual trick to create a static instance of something in a template class
        static std::function<float(float)> & get() { static std::function<float(float)> t; return t; }
        // Set the std::function instance to point to the lambda
        static void set(T & t) { CaptureLambda::get() = t; }
    };
}

// This is the adapted class that's following the expectation of 
// "cannot_change" function. It has compile-time captured the CaptureLambda::adapter function
template <func_type func>
struct Adapter
{
    // Store the result of the computation of the lambda
    float result;
    // Simplest solution. You could also compute the result in the () operator, but you'd need to store the parameter
    Adapter(float param) : result(func(param)) {}
    float operator ()() const { return result; }
};
int main()
{
    // Here's a local you want to capture
    const float local = 10;
    // Here's the lambda that's doing the work
    auto f = [local](float second) -> float { return local * second; };
    
    // Because I'm lazy typing long types, create an alias.
    typedef details::CaptureLambda<decltype(f)> CL;
    // Need to capture the lambda instance here
    CL::set(f);
    // And finally call the cannot_change template function
    std::cout << cannot_change<Adapter<CL::adapter>>(20) << std::endl;
    return 0;
}


Godbolt
与上面的注解不同,您不需要将本地global变量存储在静态变量中(这会带来很多麻烦)。
相反,它使用lambda来捕获局部变量。lambda函数示例由一个模板CaptureLambda结构捕获,该结构是为这个特定的lambda示例化的。此结构提供了一个静态函数,其签名与lambda的函数签名相匹配。
最后,Adapter结构使用此静态函数的指针示例化,并实现cannot_change所需的接口B
请注意:
1.由于每个lambda都是不同的类型(即使具有相同的签名),因此CaptureLambda静态示例上不会有任何冲突,必须将其设置为用于示例化它的相同lambda。这意味着没有问题,你可以找到与普通的静态变量

  1. Adapter是为这个CaptureLambda特定示例化的,它可以被编译器完全优化掉。
    1.我在CaptureLambda中使用了std::function<>,因为我很懒。更好的解决方案是实际复制/移动/引用lambda,以避免std::function的开销
    1.如果你的lambda没有捕获,不要麻烦使用CaptureLambda,直接在cannot_change调用中使用Adapter<decltype(f)>,因为一个没有捕获的lambda可以直接转换为一个函数指针。

相关问题