从指针到成员的映射

时间:2014-02-11 08:41:34

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

(注意:如果这感觉像是X-Y问题,请在分隔符下方滚动以了解我是如何得出这个问题的)

我正在寻找一种方法来存储指向成员函数的指针(不同类型)并将它们进行相等性比较。我需要存储从指针到成员函数到任意对象的映射,然后搜索此映射。它不必是关联容器,线性搜索就可以了。另请注意,指针仅用作映射键,它们永远不会被解除引用。

我目前的方法是:在构建映射时,我reinterpret_cast将传入的指针指向一个众所周知的类型(void (MyClass::*)())并将其插入到映射中。这样的事情(为简洁起见,省略了错误检查):

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));
}

然后在查找时,我执行相同的强制转换并按等号搜索:

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;
}

但是,我不确定这是否会一直有效。虽然我认为它不能产生假阴性(两个相等的指针被报告为不同的),但我担心它可能会产生假阴性(原来不同类型的两个指针在投射到“普通”类型时可以比较相等)。所以我的问题是,是这样吗?或者我可以安全地使用这样的比较吗?

我知道我在这里危险地靠近UB地区。但是,我不介意使用行为的解决方案,该行为不是由标准定义的,但已知在gcc和MSVC(我的两个目标编译器)中工作。

所以,问题是:普通类型的比较安全吗?或者我最好将存储的指针转换为传入类型进行比较(如下所示):

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;
}

或者这些都不会在实践中发挥作用而且我运气不好?


我对指向成员指针的上述属性感兴趣,既考虑到我的实际任务,又加深了对语言的理解。尽管如此,出于一种完整感(如果有人知道更好的方式),这就是我对原始问题的看法。

我正在构建一个实用程序框架,用于帮助对 Qt4 信号进行单元测试(测试是否发出了正确的信号)。我的想法是创建一个类CallChecker,它将存储插槽的验证器(包装std::function个对象),并能够运行它们。然后测试将创建一个派生自此的类;该类将定义将运行相应验证器的槽。这是一个使用想法(简化):

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; });
}

我有一个working implementation(关于ideone的gcc),其中CallChecker使用std::vector对,其中指针成员转换为常用函数类型。在使用编译器标志(/vmg)进行一些调整之后,我也在MSVC中工作了。

如果您可以建议一个更好的解决方案而不是通过指向成员的查找,我会很高兴听到它。我的目标是在实现测试槽的类中易于使用:我真的希望这些插槽是简单的单行。使用插槽签名的文本表示(Qt内部使用的内容)实际上不是一个选项,因为它太容易被打字错误。

4 个答案:

答案 0 :(得分:2)

正如我在评论中所说,有一种方法可以对qt信号的发射进行单元测试。您需要使用QSignalSpy并链接到QTestLib。

正如他们在文档中所说:

  

QSignalSpy可以连接任何物体的任何信号并记录其发射。 QSignalSpy本身是QVariant列表的列表。信号的每次发射都会将一个项目附加到列表中,其中包含信号的参数。

您还可以阅读他们的示例,但这是我使用Google测试的单元测试之一:

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 );
}

答案 1 :(得分:2)

比较任何东西。

#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"));
}

operator<的实施推迟给读者。

重要提示在此实现中,两个指向不同类型成员的指针总是编译不相等,但在强制转换为通用类型后,它们可能在直接比较中相等。如果&Foo::x派生自&Bar::x,则FooBar可以相同。这种行为不容易在这里添加。

答案 2 :(得分:1)

如果您首先检查双方的typeid是否相同,则可以使用类型擦除功能将两侧投射到相同类型并进行比较。 (这是标准所必需的,因为即使您可以通过一种众所周知的类型往返,也不能保证该类型的比较与原始类型的比较具有相同的行为。)这里是草图:

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);
}

答案 3 :(得分:1)

对于狭隘的问题,这是一个狭隘的答案。

标准通过暗示和脚注说明指向成员的指针不能转换为void *。可能的理由是指向成员的指针可能需要比void *更多的存储字节。你的编译器应该禁止重新解释强制转换,即使你没有真正的冲突风险也是如此。您可以对目标编译器进行测试,但风险仍然存在。

当T1和T2都是函数类型时,标准将允许您将'指向类型T1的X成员的指针'转换为'指向类型为T2的成员的指针'。换句话说,只要common类型是指向成员函数的指针,就允许使用策略。我想这就是你的意图。 N3337中的S5.2.10 / 10。然而,它并不能保证两个这样的指针在指向对象的方式上比较相等。例如,如果实现包含编码的“this”指针,则它将无效。

标准将允许您将指针存储到联合中的成员。您可以提供一个可能足够长的char []成员,并且可以在sizeof上使用断言来确保它是。如果它是“标准布局”类型,通过char []访问该值应该具有保证的行为。就个人而言,我会试着找出这些指针究竟有多大!但是关于可能的非规范价值的问题仍然存在。

我的第三个建议是你使用指针到成员函数的typeid而不是指针本身。 Typeid可以应用于任何表达式 - 如果它对于reinterpret_cast足够好,对于typeid来说就足够了 - 结果值对于类型而言应该是唯一的,而不是实例。

之后我就失去了想法。在寻求其他解决方案时,您可能需要重新定义/重新协商问题。