4

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

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

我目前的方法是这样的:在构建映射时,我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; });
}

我有一个工作实现(ideone 上的 gcc),其中CallChecker使用一std::vector对,指向成员的指针强制转换为通用函数类型。在对编译器标志/vmg

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

4

4 回答 4

2

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

正如他们在文档中所说:

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

您也可以阅读他们的示例,但这是我使用 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 );
}
于 2014-02-11T09:21:19.847 回答
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可以是相同的,如果Foo派生自Bar。这种行为不能轻易添加到这里。

于 2014-02-11T09:43:10.307 回答
1

这是对狭隘问题的狭隘回答。

该标准暗示并在脚注中指出,指向成员的指针不能转换为 void*。可能的理由是指向成员的指针可能比 void* 需要更多字节的存储空间。您的编译器应该禁止重新解释强制转换,即使不这样做,您也会面临真正的冲突风险。您可以在目标编译器上进行测试,但风险仍然存在。

当 T1 和 T2 都是函数类型时,该标准将允许您将“指向 T1 类型 X 成员的指针”转换为“指向 T2 类型 Y 成员的指针”。换句话说,只要公共类型是指向成员函数的指针,就允许您的策略。我想这就是你想要的。N3337 中的 S5.2.10/10。然而,它并不保证两个这样的指针会比较相等,就像它对指向对象的指针所做的那样。例如,如果实现包含一个编码的“this”指针,它就不起作用。

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

我的第三个建议是您使用指向成员函数的指针的 typeid 而不是指针本身。Typeid 可以应用于任何表达式——如果它对 reinterpret_cast 足够好,它对 typeid 也足够好——并且结果值应该是类型唯一的,而不是实例。

在那之后,我没有想法了。您可能必须重新定义/重新协商问题以寻求其他解决方案。

于 2014-02-11T11:05:23.640 回答
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);
}
于 2014-02-11T10:27:43.750 回答