(注意:如果这感觉像是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内部使用的内容)实际上不是一个选项,因为它太容易被打字错误。
答案 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
,则Foo
和Bar
可以相同。这种行为不容易在这里添加。
答案 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来说就足够了 - 结果值对于类型而言应该是唯一的,而不是实例。
之后我就失去了想法。在寻求其他解决方案时,您可能需要重新定义/重新协商问题。