【问题标题】:In what ways member functions may be compared to each other?成员函数可以通过哪些方式相互比较?
【发布时间】:2011-07-12 10:41:31
【问题描述】:

我想知道是否可以将 2 个成员函数与“void*,但这也不起作用。

template <class Receiver, class Sender>
class CallBack2 : public ICallBack2 {

protected:

    Receiver* receiver;
    void(Receiver::*function)(Sender*);
    Sender* sender;

public:

    CallBack2(Receiver* _receiver, void(Receiver::*_function)(Sender*), Sender* _sender) : receiver(_receiver), function(_function), sender(_sender) {};
    virtual ~CallBack2() {};

    virtual void callBack() {
        (receiver->*function)(sender);
    }

    virtual bool operator<(const ICallBack2* _other) const {
        CallBack2<Receiver, Sender>* other = (CallBack2<Receiver, Sender>*)_other;
        if (receiver < other->receiver) {
            return true;
        } else if (receiver == other->receiver && function < other->function) {
            return true; // this line gives the error
        }
        return false;
    }
};

有什么想法吗?

【问题讨论】:

  • 你为什么要这样做?
  • function1 &lt; function2 是什么意思?
  • 我需要一个恒定的比较结果才能将它们放入 std::set 或 std::map
  • @Bill Kotsias 然后实现一个返回结果的函数。您可以使用该函数或更改此函数的签名。
  • @Bill Kostias,您应该编辑您的问题以反映您实际尝试做的事情。您想将成员函数放入 std::set 或 std::map。如图所示,您尝试使用operator&lt;,但它不起作用。

标签: c++ function operator-overloading compare


【解决方案1】:

C++03 § 5.9,涵盖了内置 、= 的语义,没有提到指向成员和状态的指针:

未指定其他指针比较。

根据§ 8.3.3,3

“指向成员的指针”类型不同于“指针”类型,

因此,我们可以得出结论,将关系运算符应用于指向成员(无论是函数还是字段)的指针的结果是未指定的。

请注意,“未指定行为”与“未定义行为”不同,但仍然意味着您无法有效地应用运算符,因为不同的实现可能会产生不同的结果。 “未指定”基本上意味着实现可以定义行为。

【讨论】:

  • 幸运的是,在这种情况下它并不重要,因为“未指定其他指针比较”。指向成员函数的指针类型不是指向函数的类型,但就那句话而言,成员函数是否是函数可能是模棱两可的?我希望如果它意味着包含成员函数,它会这样说,但这是基于指针类型不同的事实,而不是基于我知道标准声明成员函数不是函数的任何地方。是的。
  • 5.10(等式运算符)明确处理成员函数,关于函数的措辞是一样的。所以这意味着@Steve 可能是对的:“未指定其他指针比较”
  • “未指定”基本上并不意味着“实现定义”,如果您可以访问实现文档,因为前者不必记录,而后者则需要。但是,在尝试编写可移植代码时,您通常必须避免使用这两种代码,除非标准将行为限制在您可以处理的范围内,例如“未指定 X 还是 Y”,并且您对其中任何一个都感到满意.
  • 指向成员函数的指针确实不被视为指向函数的指针。已更新答案以引用与问题相关的标准部分。
  • 在这种情况下,“未指定”意味着比较运算符肯定总是返回truefalse,但我们无法预测返回值。将我们从 UB 中拯救出来,但无论如何都没有用。
【解决方案2】:

如果您只是想任意命令它们成为集合/映射中的键,那么您可以reinterpret_cast 它们。您可能需要像 exact_int&lt;sizeof(void (Foo::*bar)())&gt;::type 这样的模板类,因为 pointers to member functions can have funny sizes

【讨论】:

  • 我认为这也行不通。 AFAIK 不能保证 reinterpret_casts 的相等指针到成员函数值是相等的。在 ptr-to-mem-fun 中可能有填充字节/位,它们采用任意值并被 operator== 忽略,用于 ptr-to-mem-fun,但不会被 reinterpret_cast 忽略。
【解决方案3】:

5.9.7(关系运算符):“未指定其他指针比较”。

由于 5.9 不清楚(它处理函数,但不明确处理成员函数),快速浏览 5.10(相等比较)清楚地将 函数成员函数 区分开来:

另外,可以比较指向成员的指针,或者指向 成员和一个空指针常量。指向成员转换的指​​针 (4.11)和资格转换(4.4)被执行以带来他们 为普通类型。如果一个操作数是空指针常量,则 普通类型是另一个操作数的类型。否则,常见的 type 是指向成员类型的指针,类似于 (4.4) 的类型之一 具有 cv 限定签名 (4.4) 的操作数,即 操作数类型的 cv 限定签名的联合。 [笔记: 这意味着任何指向成员的指针都可以与 null 进行比较 指针常量。 ] 如果两个操作数都为空,它们比较相等。 否则,如果只有一个为空,则它们比较不相等。否则如果 要么是指向虚成员函数的指针,结果是 未指定。否则它们比较相等当且仅当它们会 引用同一个最衍生对象(1.8)的同一个成员或 如果它们被一个假设的对象取消引用,则相同的子对象 关联的类类型。

所以你可以使用运算符,==!=的含义是指定的,但&lt;&gt;&lt;=&gt;=的含义是未指定的。

特别是,没有任何东西强制传递性,所以不清楚将它们放在一个集合中是否可以。

【讨论】:

    【解决方案4】:

    虽然描述有点冗长,但有一个假人怎么样 变量并比较它的指针如下?

    template< class T >
    struct comparable_value {
        T value;
        char *id;
    
        comparable_value( T value, char* id ) : value( value ), id( id ) {}
    
        bool operator<( comparable_value const& x ) const {
            return std::less< char* >()( id, x.id );
        }
    };
    
    template< class T, T V >
    comparable_value< T > get_comparable_value() {
        static char dummy;
        return comparable_value< T >( V, &dummy );
    }
    
    struct A {
        void f() { puts( "f" ); }
        void g() { puts( "g" ); }
    };
    
    int main() {
        typedef void (A::*MF)();
        typedef std::set< comparable_value< MF > > set_t;
        set_t s;
        s.insert( get_comparable_value< MF, &A::f >() );
        s.insert( get_comparable_value< MF, &A::g >() );
        A a;
        for ( set_t::iterator i = s.begin(), e = s.end();  i != e;  ++ i )
            (a.*i->value)();
    }
    

    这是对ideone 的测试。

    【讨论】:

    • 看起来类似的东西必须使用 ptr-to-mem-fun 作为编译时常量(将其用作模板参数)来获取,所以如果提供 ptr 的人在第一名也可以提供与之相当的等价物。
    • @SteveJessop:是的,如果OP采用这种方式,问题中CallBack2中的function成员必须是comparable_value&lt; void(Receiver::*)(Sender*) &gt;而不是成员指针本身,并且@987654326的用户@ 在构造CallBack2 时必须调用get_comparable_value。但是,我不确定这是否真的适用于 OP 的情况。即使可能,代码也会变得很长。
    【解决方案5】:

    您可以执行 Ise 的想法,只需将其包含在 Callback2 类中,这样您就无需更改使用该类的任何内容。

    template <class Receiver, class Sender>class CallBack2 : public ICallBack2 {
    private:
       static int nextSequenceNumber;
       int sequenceNumber;
    
    //snip
    
    public:
      CallBack2(Receiver* _receiver, void(Receiver::*_function)(Sender*), Sender* _sender) :
       sequenceNumber(nextSequenceNumber++), receiver(_receiver), function(_function), sender(_sender) {};
    
    //snip
    
    virtual bool operator<(const ICallBack2* _other) const {
      return sequenceNumber<_other->sequenceNumber;}
    

    【讨论】:

    • 好主意,但是当 Receiver 和 Receiver::*function 相等时,它不能说两个 CallBack2 对象是相同的。它总是假设它们是不同的,这是不受欢迎的行为。
    • @Bill 您可以在测试序列号之前测试等效性。
    【解决方案6】:

    获取成员函数的地址会导致无法存储在变量中的常量表达式。它只能用于与另一个表达式比较是否相等,该表达式表示一个函数的地址,该函数采用相同的参数集,具有相同的返回类型和相同的 this 指针类型。

        class a 
        {
        public:
            virtual void test();
        };
        class b
        {
        public:
            virtual void test();
        };
    
        ....
        a *pa = new a;
        b *pb = new b;
    
        if (pb->test == pa->test) // legal, but  false
        if (pb->test==pb::test) // legal and true
        // pa->test will evaluate to a::test, although calling pa->test() would call
        // b::test()
    

    【讨论】:

    • void (a::*pfa)(void) = &amp;a::test;。指向成员函数的指针,存储在变量中。
    • @Steve,谢谢,我不知道!然后,您将如何使用 pfa 在对象上调用它?只是好奇。
    • (pa-&gt;*pfa)()-&gt;*.* 是指向成员的指针取消引用运算符。助记符是*pfa 是成员,因此pa-&gt;*pfa 访问该成员(通过指针pa),但实际上-&gt;* 是单个运算符。
    • @steve,您是否有意在 &a::test 中使用“&”?我认为提到一个没有'()'的函数正在获取地址,所以 a::test 是成员函数的地址。如果你写&pa->test,编译器会在运行时生成代码来查询'pa'的虚方法表吗?
    • 是的,我是认真的,虽然我认为你是对的,但可以省略。是的,调用 ptr-to-mem-fun 函数是虚拟的,可以用来进行虚拟调用。
    【解决方案7】:

    比较两个函数指针是没有意义的。您可以比较的实际上是这些函数的返回值:

    *function(sender) < *(other->function)(sender)
    

    但在您的情况下,您将函数声明为:

    void(Receiver::*function)(Sender*);
    

    所以,在我看来。比较功能是没有用的。要么更改函数的签名以返回某些内容,要么更好地描述您的业务场景,以便我们更好地了解您想要什么。

    【讨论】:

    • 比较函数指针不是没用,问题说明了它的作用——在Callback2上定义operator&lt;,大概是为了和==一致。你是对的,比较“没有意义”,但是比较不指向公共对象的子对象的指针也是没有意义的,但是标准为此目的提供了 std::less 指针类型:关联容器只需要一致性,不需要意义。但这仅适用于指针类型,而不适用于 ptr-to-mem-fun 类型。函数调用的返回值与提问者想要什么无关。
    • 感谢您的评论。使用关系运算符比较对象的指针仅对驻留在同一数组中的对象有意义(因此比较它们的相对位置),但在这种情况下则不然。这就是为什么我建议实现一个返回有效结果的单独函数。我仍然不明白你为什么要-1这个。这里有几个链接来支持这个论点:publications.gbdirect.co.uk/c_book/chapter5/…msdn.microsoft.com/en-us/library/ya5wt12d.aspx
    • std:less&lt;T*&gt; 适用于 all 指向对象的指针,而不仅仅是 p1 &lt; p2 适用的指针。原因是您可以拥有一个setmap,其键类型是指针,并且其成员并不都在同一个数组中。但是 ptr-to-mem-fun 缺少类似的额外要求,不是因为它没有任何意义,而是(我认为)因为预计它更难实现。无论如何,当 OP 的代码清楚地显示所需用途时,-1 主要用于“IMO,比较是无用的”。
    猜你喜欢
    • 2012-02-25
    • 2019-09-05
    • 1970-01-01
    • 2020-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-03
    • 1970-01-01
    相关资源
    最近更新 更多