Mapping from pointers-to-member

*爱你&永不变心* 提交于 2019-12-06 08:29:55

问题


(Note: in case this feels like an X-Y problem, scroll below the separator for how I arrived at this question)

I am looking for a way to store pointers-to-member-functions (of different types) and compare them for equality. I need to store a mapping from pointer-to-member-function to an arbitrary object, and then search this mapping. It doesn't have to be an associative container, a linear search is fine. Also note that the pointers serve as mapping keys only, they are never dereferenced.

My current approach is this: when building the mapping, I reinterpret_cast the incoming pointer-to-member to one well-known type (void (MyClass::*)()) and insert it into the mapping. Something like this (error checking omitted for brevity):

template <class R, class... A)
void CallChecker::insert(R (MyClass::*key)(A...), Object value)
{
  mapping.push_back(std::make_pair(reinterpret_cast<void (MyClass::*)()>(key), value));
}

Then on lookup, I perform the same cast and search by equality:

template <class R, class... A)
Object CallChecker::retrieve(R (MyClass::*key)(A...)) const
{
  auto k = reinterpret_cast<void (MyClass::*)()>(key);
  auto it = std::find_if(begin(mapping), end(mapping), [k](auto x) { return x.first == k; });
  return it->second;
}

However, I am not sure that this will always work. While I believe it cannot produce false negatives (two equal pointers being reported as distinct), I am afraid it might produce false negatives (two pointers which were originally of different type could compare equal when cast to the "common" type). So my question is, is that the case? Or am I safe in using comparisons like this?

I know I am treading dangerously close to UB territory here. However, I don't mind a solution which works using behaviour which is not defined by the standard, but known to work in gcc and MSVC (my two target compilers).

So, the question is: is the comparison in a common type safe? Or would I be better off casting the stored pointer to the incoming type for the comparison (like this):

template <class R, class... A)
Object CallChecker::retrieve(R (MyClass::*key)(A...)) const
{
  auto it = std::find_if(begin(mapping), end(mapping), [key](auto x) { return reinterpret_cast<R (MyClass::*)(A...)>(x.first) == key; });
  return it->second;
}

Or will neither of these work in practice and I'm out of luck?


I am interested in the above properties of pointers-to-member, both in light of my actual task and to deepen my understanding of the language. Still, out of a sense of completeness (and in case somebody knows a better way), here is how I arrived at the original question.

I'm building a utility framework for helping unit-testing Qt4 signals (testing that the proper signals are emitted). My idea was to create a class CallChecker that would store validators (wrapped std::function objects) for slots, and be able to run them. The test would then create a class derived from this; that class would define slots which would run the corresponding validators. Here's an idea of usage (simplified):

class MyTester : public QObject, public CallChecker
{
  Q_OBJECT
public slots:
  void slot1(int i, char c) { CallChecker::checkCall(&MyTester::slot1, i, c); }
  void slot2(bool b) { CallChecker::checkCall(&MyTester::slot2, b); }
};

void testRunner()
{
  MyTester t;
  connectToTestedSignals(t);
  t.addCheck(&MyTester::slot1, [](int i, char c) { return i == 7; });
}

I have a working implementation (gcc on ideone) where CallChecker uses a std::vector of pairs, with the pointers-to-member cast to a common function type. After some fiddling with compiler flags (/vmg), I got this working in MSVC as well.

If you can suggest a better solution than lookup by pointer to member, I'll be happy to hear it. My goal is ease of use in the class implementing the test slots: I really want these slots to be simple one-liners. Using a textual representation of the slot signature (what Qt uses internally) is not really an option, as it's too susceptible to typos.


回答1:


If you first check that the typeid of both sides are the same, you can then use a type-erased function to cast both sides to the same type and compare in that type. (This is strictly necessary by the standard, as even if you can round-trip via a well-known type, there is no guarantee by the standard that comparisons in that type will have the same behaviour as comparisons in the original type.) Here's a sketch:

struct any_pmf_compare {
    std::type_index ti;
    void (any_pmf_compare::*pmf)();
    bool (*comp)(const any_pmf_compare &, const any_pmf_compare &);
    template<typename F>
    any_pmf_compare(F f):
        ti(typeid(F)),
        pmf(reinterpret_cast<void (any_pmf_compare::*)()>(f)),
        comp([](const any_pmf_compare &self, const any_pmf_compare &other) {
            return reinterpret_cast<F>(self.pmf) == reinterpret_cast<F>(other.pmf);
        })
    {
    }
};
bool operator==(const any_pmf_compare &lhs, const any_pmf_compare &rhs) {
    return lhs.ti == rhs.ti && lhs.comp(lhs, rhs);
}



回答2:


As I said in the comments, there is a way to unit test that a qt signal is emitted. You need to use QSignalSpy and link to QTestLib.

As they say in their documentation :

QSignalSpy can connect to any signal of any object and records its emission. QSignalSpy itself is a list of QVariant lists. Each emission of the signal will append one item to the list, containing the arguments of the signal.

You can also read their examples, but here is one of my unit tests that use google test :

class TestSomeControls : public testing::Test
{
public:

    TestSomeControls() :
        obj(),
        ctrl1Dis( &obj, SIGNAL(DisableControl1(bool)) ),
        ctrl2Dis( &obj, SIGNAL(DisableControl2(bool)) )
    {
    }

    model::SomeControls obj;

    QSignalSpy ctrl1Dis;
    QSignalSpy ctrl2Dis;
};

TEST_F( TestSomeControls, OnControl1Clicked_untilControl1Disabled )
{
    for ( int i = 0; i < 5; ++ i )
    {
        obj.OnControl1Clicked();
        ASSERT_EQ( ctrl1Dis.count(), 0 );
    }

    obj.OnControl1Clicked();

    ASSERT_EQ( ctrl1Dis.count(), 1 );
    ASSERT_EQ( ctrl1Dis.takeFirst().at(0).toBool(), true );
}



回答3:


Compare anything to anything.

#include <utility>
#include <memory>
#include <iostream>

struct Base
{
  virtual bool operator== (const Base& other) const = 0;
  virtual ~Base() {}
};

template <class T>
struct Holder : Base
{
  Holder(T t) : t(t) {}
  bool operator== (const Base& other) const
  {
    const Holder<T>* h = dynamic_cast<const Holder<T>*>(&other);
    return (h && h->t == t);
  }
  private:
  T t;
};

struct Any
{
  template<class T>
    Any(T t) : p(std::make_shared<Holder<T>>(t)) {}
  bool operator== (const Any& other) const
  {
    return *p == *other.p;
  }
  private:
  std::shared_ptr<Base> p;
};

int main ()
{
  std::cout << (Any(2) == Any(2));
  std::cout << (Any(2) == Any(3));
  std::cout << (Any(2) == Any("foo"));
  std::cout << (Any("foo") == Any("foo"));
  std::cout << (Any("foo") == Any("bar"));
}

Implementation of operator< is deferred to the reader.

Important note Two pointers-to-member of different types will always compile unequal in this implementation, but it is possible that they will be equal in direct comparison after coercion to a common type. I.e &Foo::x and &Bar::x can be the same if Foo derives from Bar. Such behaviour cannot be easily added here.




回答4:


This is a narrow answer to the narrow question.

The standard states by implication and also in a footnote that a pointer to member cannot be converted to void*. The likely rationale is that a pointer to member could require more bytes of storage than a void*. Your compiler should forbid the reinterpret cast, and even it if does not you run a real risk of clashes. You can test on your target compilers, but the risk remains.

The standard will permit you to convert a 'pointer to member of X of type T1' to 'pointer to member of Y of type T2' when T1 and T2 are both function types. In other words, your strategy is permitted as long as the common type is a pointer to member function. I think this is what you intended. S5.2.10/10 in N3337. It does not however guarantee that two such pointers will compare equal, in the way that it does for pointers to objects. For example, if the implementation includes an encoded 'this' pointer, it just won't work.

The standard will permit you to store the pointer to member in a union. You can provide a char[] member that is likely to be long enough, and you can use an assert on sizeof to make sure that it is. Provided it's a 'standard layout' type, accessing the value through the char[] should have guaranteed behaviour. Personally, I would try this just to find out how big those pointers actually are! But the problem about possible non-canonical values remains.

My third suggestion is that you use the typeid of the pointer-to-member-function instead of the pointer itself. Typeid can be applied to any expression -- if it's good enough for reinterpret_cast it's good enough for typeid -- and the resultant value should be unique to the type, not the instance.

After that I'm out of ideas. You might have to redefine/renegotiate the problem in a quest for other solutions.



来源:https://stackoverflow.com/questions/21696881/mapping-from-pointers-to-member

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!