【问题标题】:Safe way to reference nested member引用嵌套成员的安全方法
【发布时间】:2020-05-05 18:42:47
【问题描述】:

我有一个struct 和其他一些structs 作为成员。外部和内部结构都是StandardLayout(甚至可以假设内部是普通的旧数据)。像这样的:

struct Inner1 {
    int a = 0, b = 0;
};
struct Inner2 {
    int c = 0, d = 0;
};
struct Outer {
    Inner1 x;
    std::string s;
    Inner2 y;
};

我想编写一些函数,它采用 Outer& 和某种类型的对象 T,可以返回任何嵌套字段的值,具体取决于参数:

int get(Outer& o, T field);

如果Outer 是一个平面结构,指向成员的指针正是我所需要的,但它不是平面结构。

简单的方法是将所有字段的T 设为enum 并写一个switch,但我认为它效率不高。更快的方法是使T 偏移并写入

int get(Outer& o, size_t field) {
    return *reinterpret_cast<int*>(reinterpret_cast<char*>(o) + field);
}

然后将其称为get(o, offsetof(Outer, y) + offsetof(Inner2, c))。它可以工作,但我不确定它是否可以保证工作 - 像这样对偏移量求和是否正确,以及通过偏移量获取成员值是否安全。

那么问题来了:这种方式安全吗?如果没有,有没有安全的方法?构造T 的值可以是任意复杂的,但是使用它们应该很快。

动机:我需要将一些嵌套字段中的值按某种顺序放置,在启动时已知,但在编译时不知道。我想在启动时创建一个 T 数组,然后在获取特定对象时,使用这个预先计算的数组。

[UPD]:所以会这样使用:

void bar(int);
void foo(Outer& o, vector<T>& fields) {
    for (auto& field : fields) {
        bar(get(o, field));
    }
}

【问题讨论】:

  • 我没有看到使用偏移量的优势。它要求调用者知道该偏移量,因此您也可以将指向成员的指针作为参数,以避免与reinterpret_casts
  • 谢谢,但我认为它们不适用于嵌套成员。
  • 使用offsetof 应该没问题。这是唯一编译器支持的获取包含成员地址的方法,因为没有关于将包含的位置和内容的标准。但是offsetof 是一个 C 函数。为什么你认为enumswitch 很慢而且有点令人困惑。很难看到这种情况还不够。
  • 可能有几百个字段,而switch是线性时间运行的,所以会很慢。
  • @mihaild 我希望开关优化为查找表,因此它可能是恒定时间。

标签: c++ pointer-to-member offsetof


【解决方案1】:

你可以这样做。

/* main.cpp */

#include <string>
#include <iostream>

using namespace std;

struct Inner1 {
    int a = 0, b = 0;
};

struct Inner2 {
    int c = 0, d = 0;
};

struct Outer {
    Inner1 x;
    std::string s;
    Inner2 y;
};

struct OuterMember
 {
  int (*getter)(Outer &obj);
 };

inline int get(Outer &obj,OuterMember field) { return field.getter(obj); }

template <auto Ptr1,auto Ptr2>
auto GetInnerMember(Outer &obj) { return (obj.*Ptr1).*Ptr2; }

inline constexpr OuterMember OuterMemberA = { GetInnerMember<&Outer::x,&Inner1::a> } ; 

inline constexpr OuterMember OuterMemberB = { GetInnerMember<&Outer::x,&Inner1::b> } ; 

inline constexpr OuterMember OuterMemberC = { GetInnerMember<&Outer::y,&Inner2::c> } ; 

inline constexpr OuterMember OuterMemberD = { GetInnerMember<&Outer::y,&Inner2::d> } ; 

/* main() */

int main()
 {
  Outer obj;

  obj.x.a=1;
  obj.x.b=2;
  obj.y.c=3;
  obj.y.d=4;

  cout << "a = " << get(obj,OuterMemberA) << endl ;
  cout << "b = " << get(obj,OuterMemberB) << endl ;
  cout << "c = " << get(obj,OuterMemberC) << endl ;
  cout << "d = " << get(obj,OuterMemberD) << endl ;

  return 0;
 }

【讨论】:

    【解决方案2】:

    我确实认为这是安全的(因为不违反严格的别名)。

    但是,该语言确实有一个更好的机制来做到这一点:指向数据成员的指针,它编译成基本上是一个偏移量。

    需要注意的是,您必须为 Inner1Inner2 分别进行重载

    int get(Outer& o, int Inner1::* m) {
        return o.x.*m;
    }
    
    int get(Outer& o, int Inner2::* m) {
        return o.y.*m;
    }
    
    int foo() {
      Outer tmp;
      return get(tmp, &Inner1::a) + get(tmp, &Inner2::d);
    }
    
    

    【讨论】:

    • 谢谢,但如果我在打电话给get 时知道我是否需要xy,这会起作用,但我不知道。
    【解决方案3】:

    您可以使用功能模板专业化实现相同的功能,请参阅下面的示例代码

    #include <iostream>
    
    using namespace std;
    
    struct Inner1 {
    int a = 1, b = 2;
    };
    struct Inner2 {
    int c = 3, d = 4;
    };
    struct Outer {
    Inner1 x;
    std::string s;
    Inner2 y;
    };
    
    template<typename T>
    int get(Outer&o);
    
    template<>
    int get<Inner1>(Outer& o)
    {
     return o.x.a;
    }
    
    template<>
    int get<Inner2>(Outer& o)
    {
      return o.y.c;
    }
    
    int main()
    {
      Outer out;
      std::cout << get<Inner1>(out)  << std::endl;
      std::cout << get<Inner2>(out)  << std::endl;
    
      return 0;
    }
    

    我希望这会有所帮助!更有趣的是,这是类型安全的。

    【讨论】:

    • 当您所做的只是针对所有可能的Ts 进行专门化时,使用模板的意义何在?两个不同命名的函数可以用更少的代码做同样的事情
    • 1.因为编译器本身可以帮助您在编译时检测错误。 2. 而且模板化的功能只有在你使用的时候才会生成。 3. 你不会在运行时看到惊喜。
    • 你得到 1 和 3 也没有模板,单独 2 并不是将所有内容都变成模板的好理由恕我直言
    • 小误会:我不是发这个问题的人。我只是在这里表达了我对模板有用性的怀疑。使用两个不同命名的函数,您编写的代码将与现在一样安全且速度快(而且更短)
    • 对不起,我修改了我的评论。我相信我们在这里谈论的是同一件事,但有两种不同的设计。每个人都有自己的优势。
    猜你喜欢
    • 2011-04-09
    • 2014-11-08
    • 2015-11-02
    • 2018-11-24
    • 1970-01-01
    • 1970-01-01
    • 2011-01-29
    相关资源
    最近更新 更多