【问题标题】:compare a pointer to member?比较指向成员的指针?
【发布时间】:2012-10-25 09:47:27
【问题描述】:

我很困惑为什么不能使用二元运算符比较指向成员的指针

class Point3d{
  protected:
      //..
 public:
     float x;
      static list<Point3d*> *freeList;
 public:
     float y;
     static const int chunkSize = 250;
 public:
    float z;

};
和一个模板:

template< class class_type, class data_type1, class data_type2 >

char* access_order(data_type1 class_type:: *mem1, data_type2 class_type:: *mem2)
{

    return
      mem1 < mem2 ?
         "member 1 accurs first":
         "member 2 accurs first";
}  

当我像下面这样调用 access_order 时:

access_order(&Point3d::z, &Point3d::y);

g++ 报告:

"invalid operands of types ‘float Point3d::*’ and ‘float Point3d::*’ to binary ‘operator<’"

有没有办法比较指向成员的指针,我的意思是不等比较,以及如何?

【问题讨论】:

  • 出于同样的原因,您不应该使用运算符比较指针
  • @FrerichRaabe 我不认为这是重复的,因为他询问的是指向数据成员的指针,而不是指向成员函数的指针。 (此外,您引用的线程中的主要答案不正确。)
  • @JamesKanze:对不起,我看错了——我以为另一个问题是“指向成员的指针”。我同意这不是重复的。我可以在这里恢复我的关闭请求吗?

标签: c++


【解决方案1】:

您可以比较对象成员的地址

A a;
if (std::less<void*>()(&a.a, &a.b))
    std::cout << "a precedes b\n";
else
    std::cout << "a follows b\n";

【讨论】:

    【解决方案2】:

    最好的选择之一 - 通过std::memcpy 制作原始副本,计算哈希,然后将其用于比较(感谢@HolyBlackCat 的 cmets)。下面的函数计算传递的指向成员的指针的哈希值(在现代 C++ 17 编译器 VS、GCC.CLang 上测试)。

    #include <cstring>
    #include <string_view>
    #include <functional>
    
    template <typename TObject, typename TMember>
    size_t HashMemberPtr(TMember TObject::* memberPtr)
    {
        char buf[sizeof memberPtr];
        std::memcpy(&buf, &memberPtr, sizeof memberPtr);
        return std::hash<std::string_view>{}(std::string_view(buf, sizeof buf));
    }
    

    很遗憾,它与std::hash&lt;&gt; 不兼容,因为最后一个模板参数只需要一个。

    使用方法:

    struct CPoint3D
    {
        float x;
        float y;
        float z;
    };
    
    int main()
    {
        const size_t xHash = HashMemberPtr(&CPoint3D::x);
    
        assert(xHash == HashMemberPtr(&CPoint3D::x));
        assert(xHash != HashMemberPtr(&CPoint3D::y));
        assert(xHash != HashMemberPtr(&CPoint3D::z));
    
        return 0;
    }
    

    【讨论】:

    • 以这种方式使用联合会导致未定义的行为(即使大多数编译器不会对此做任何事情)。一个合法的替代方法是将std::memcpy 转换成一个字节数组,或者std::bit_cast
    • @HolyBlackCat ,感谢您指出替代方案,它们也可以工作并且可以使用。关于未定义的行为,从我的角度来看,所有这些 三种 方法都是一样的——允许访问指向成员的原始表示。当然,这种表示在不同的编译器中可能会有所不同,我们不能将这些数据用于任何其他目的(例如作为偏移量),但只能作为哈希
    • 在这种特定情况下,实际上所有三个都做同样的事情,因为至少 GCC 在其手册中承诺接受这种 UB,而其他编译器似乎也在做同样的事情。但总的来说,依赖 UB 的结果,因为您观察到它在单台机器上的一个编译器上工作是一个坏主意。
    • 我已经更新了答案,删除了可疑的转换,但仍然喜欢使用 union,因为带有 std::memcpy 的选项不能在 CLangstd::bit_cast 上编译,需要 C++ 20.
    • 请注意,如果您处理虚函数,您可能会得到“令人惊讶”的结果:您可以在基类和派生类中为虚函数获得相同的哈希值。
    【解决方案3】:

    出于同样的原因,您一般不能比较指针。唯一的 支持的顺序比较是如果两个数据指针指向 进入同一个数组。否则,比较的结果是 未指定;不需要编译器来使运算符“工作” 任何合理的方式。 (确保这里的总订单需要额外的 在某些架构上进行计算。)因为没有案例你可以得到 为指向成员的指针指定结果,标准不允许 它们作为运算符的参数。

    如果您需要总订购,std::less 等。保证提供 它。包括,如果我正确理解标准,成员指针。 (尽管为指向成员函数的指针提供了总排序 可能会非常昂贵。)然而,即使那样,这种订购 可能是任意的;它当然不需要反映任何 在内存中排序。

    【讨论】:

      【解决方案4】:

      如果 Point3d 重载

      返回 *mem1

      或更改签名

      char* access_order(data_type1 class_type:: &mem1, data_type2 class_type:: &mem2) char* access_order(data_type1 class_type:: mem1, data_type2 class_type:: mem2)

      您是否要对实际内存地址进行比较?

      【讨论】:

      • 好的,我只是想测试/检查一下c++内存模型,非常感谢~~~~
      【解决方案5】:

      指向成员的指针本身并不指向某些内存。它们只是标签。您唯一可以对它们做的事情就是使用运算符.*-&gt;* 将它们转换为对给定对象的指针值的引用,或者存储在另一个指向成员变量的指针中。

      struct A
      {
          int a;
          float b;
      };
      A a;
      int A::* p2m = &A::a;
      int A::* p2m2 = p2m;
      
      int & realPointer = a.*p2m;
      

      注意,你只能比较相同类型的指针,所以你不能比较指向A::aint A::*)的指针和指向A::bfloat A::*)的指针

      【讨论】:

        【解决方案6】:

        指针比较似乎对每个人都很有吸引力,但它们总是会导致不可移植或未定义的行为。

        最简单的答案是你永远不应该这样做。没有经典方法无法解决的计算问题。

        不要误会我的意思,我知道提出错误的问题可能会产生有趣的想法,或者加深对语言甚至 CPU 工作原理的理解。

        【讨论】:

          猜你喜欢
          • 2018-10-18
          • 2010-11-22
          • 2018-03-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-05-02
          • 2023-03-12
          • 2021-12-03
          相关资源
          最近更新 更多