有没有办法将对象的字段隐式传递给函数C++

Is there a way to pass an object's field implicitly to a function in C++

本文关键字:C++ 函数 字段 对象 有没有      更新时间:2023-10-16

假设我有一个对象

class Obj {
public:
    int a;
    int b;
    int c;
}

和一组对象

Obj o[N];

我想把每个Obj.a复制到一个int数组中,我知道其他语言允许我在C++中生成一个函数,它可能看起来像这样

int & fun(Obj os[], T key, int N){
    int a[N];
    for (int i=0; i<N; i++) {
       a[i] = os[i].key;
    }
    return a;
}

在C++中有什么可重复使用的方法吗?作为参考,Obj的代码不能修改。

这就是std::transform函数的作用。您所需要提供的只是一个从Obj中获取所需元素的函数。此示例显示如何使用std::mem_fn:

#include <algorithm>
#include <functional>
#include <iterator>    
#include <iostream>
struct Obj { int a, b, c; };
int main() {
    Obj o[3] = {{1, 2, 3}, {11, 22, 33},{111, 222, 333}};
    int a[3];
    std::transform(std::begin(o), std::end(o),
                   std::begin(a),
                   std::mem_fn(&Obj::a));
    for (auto e : a)
        std::cout << e << ' ';
    std::cout << std::endl;
};

输出:

1 11 111

这一切都可以封装在一个助手函数中,以允许调用方设置要提取的属性。但请注意,如果您真的希望函数返回数组,则需要使用可复制类型,如std::arraystd::vector。在C++中,普通数组是不可复制的,因此不能由函数的值返回。

这里有一个稍微修改过的代码版本:

#include <cstddef>
#include <iostream>
using std::size_t;
struct Obj {
    int a;
    int b;
    int c;
};
static void fun(const Obj *os, size_t N, int Obj::*const key) {
    for (size_t i = 0; i < N; i++) {
        std::cout << os[i].*key << 'n';
    }
}
int main() {
    Obj o[] = { {1, 2, 3}, {4, 5, 6} };
    fun(o, sizeof o / sizeof o[0], &Obj::b);
}

我更改了fun的返回类型,因为您的版本没有进行类型检查。出于演示目的,fun只是输出元素。

这里的关键是,您可以通过使用成员指针对类字段进行抽象。

您可以使用指向成员的指针语法:

#include <iostream>
#include <memory>
struct Obj {
    int a;
    int b;
};
std::unique_ptr<int[]>
fn(Obj* os, size_t N, int Obj::*member)
{
    auto arr = std::make_unique<int[]>(N);
    for (size_t i = 0; i < N; ++i) {
        arr[i] = os[i].*member;
    }
    return arr;
}
int main() {
    Obj os[] { { 1, 10 }, { 2, 20 } };
    auto a1 = fn(os, 2, &Obj::a);
    auto a2 = fn(os, 2, &Obj::b);
    for (size_t i = 0; i < 2; ++i) {
        std::cout << i << ": " << a1[i] << ", " << a2[i] << 'n';
    }
}

演示:http://ideone.com/cQMyh3

或者你可以使用lambda。

#include <iostream>
#include <memory>
struct Obj {
    int a;
    int b;
};
std::unique_ptr<int[]>
fn(Obj* os, size_t N, std::function<int(const Obj&)> keyFn)
{
    auto arr = std::make_unique<int[]>(N);
    for (size_t i = 0; i < N; ++i) {
        arr[i] = keyFn(os[i]);
    }
    return arr;
}
int main() {
    Obj os[] { { 1, 10 }, { 2, 20 } };
    auto a1 = fn(os, 2, [](const Obj& o){ return o.a; });
    auto a2 = fn(os, 2, [](const Obj& o){ return o.b; });
    for (size_t i = 0; i < 2; ++i) {
        std::cout << i << ": " << a1[i] << ", " << a2[i] << 'n';
    }
}

http://ideone.com/9OvTzl

或者更通用的:

template<typename KeyFn>
std::unique_ptr<int[]>
fn(Obj* os, size_t N, KeyFn keyFn)
{
    auto arr = std::make_unique<int[]>(N);
    for (size_t i = 0; i < N; ++i) {
        arr[i] = keyFn(os[i]);
    }
    return arr;
}

其他需要考虑的变化:

  • 使用标准容器,例如vectorarray来容纳对象
  • 考虑在范围上传递迭代器,而不是指针和大小

使用范围库,您可以简单地执行

for (int e : objs | ranges::v3::view::transform(&Obj::a)) {
    std::cout << e << " ";   
}

演示

您的例子在C++中的一个可能的转录可能是:

#include <functional>
int* fun(Obj os[], std::function<int(Obj)> get_key, int N){
    int* a = new int[N];
    for (int i=0; i<N; i++) {
        a[i] = get_key(os[i]);
    }
    return a;
}

用途:

func(os, [](Obj obj){return obj.a;}, N);

您必须声明一个动态数组,因为大小是可变的。但是使用原始指针,可能会出现内存泄漏或分段错误。带有托管指针的版本(仅适用于C++14):

#include <memory>
#include <functional>
std::unique_ptr<int[]> fun(Obj os[], std::function<int(Obj)> get_key, int N){
    std::unique_ptr<int[]> a = std::make_unique(N);
    for (int i=0; i<N; i++) {
        a[i] = get_key(os[i]);
    }
    return a;
}

或者使用stl容器(无内存分配):

#include <vector>
#include <functional>
std::vector<int> fun(Obj os[], std::function<int(Obj)> get_key, int N){
    std::vector<int> a(N);
    for (int i=0; i<N; i++) {
        a[i] = get_key(os[i]);
    }
    return a;
}

但从一种语言"翻译"到另一种语言通常是个坏主意;)