注意:
Signal1<A1>::raise(7)
Slot::operator()(7)
Slot1<A1>::operator()(7)
Handling new value: 3931764 // This value changes on each execution
Signal1<A1>::raise(7)
Slot::operator()(7)
Slot1<A1>::operator()(7)
Handling new value: 7
Signal1<A1>::raise(7)
Slot::operator()(7)
Slot1<A1>::operator()(7)
Handling new value: 7
#include <functional>
#include <iostream>
#include <vector>
// Version 1: uses perfect forwarding with std::function<void (T)>
// Version 2: uses perfect forwarding with std::function<void (const T&)>
// Version 3: forgoes perfect forwarding with std::function<void (T)>
#define VER 1
class Slot
{
public:
template<typename A1>
#if VER == 1 || VER == 2
void operator()(A1&& a1) const;
#elif VER == 3
void operator()(A1 a1) const;
#endif
};
template<typename A1>
class Slot1 : public Slot
{
public:
template<typename T>
Slot1(T* instance, void (T::*fn)(A1)) :
mFn(std::bind(fn, instance, std::placeholders::_1))
{
// Do nothing
}
void operator()(A1 a1) const
{
std::cout << "Slot1<A1>::operator()(" << a1 << ")\n";
mFn(a1);
}
private:
#if VER == 1 || VER == 3
std::function<void (A1)> mFn;
#elif VER == 2
std::function<void (const A1&)> mFn;
#endif
};
template<typename A1>
#if VER == 1 || VER == 2
void Slot::operator()(A1&& a1) const
#elif VER == 3
void Slot::operator()(A1 a1) const
#endif
{
std::cout << "Slot::operator()(" << a1 << ")\n";
#if VER == 1 || VER == 2
static_cast<const Slot1<A1>&>(*this)(std::forward<A1>(a1));
#elif VER == 3
static_cast<const Slot1<A1>&>(*this)(a1);
#endif
}
class Signal
{
public:
void connect(Slot* slot)
{
mSlots.push_back(slot);
}
std::vector<Slot*> mSlots;
};
template<typename A1>
class Signal1 : public Signal
{
public:
void raise(A1 a1)
{
std::cout << "Signal1<A1>::raise(" << a1 << ")\n";
(*mSlots[0])(a1);
}
};
class Model
{
public:
void setValue(int value)
{
mValue = value;
mValueChangedSignal.raise(value);
}
Signal1<int> mValueChangedSignal;
private:
int mValue;
};
class View
{
public:
void handleChange(int value)
{
std::cout << "Handling new value: " << value << "\n";
}
};
int main()
{
View view;
Slot1<int> slot(&view, &View::handleChange);
Signal1<int> signal;
signal.connect(&slot);
signal.raise(7);
return 0;
}
这是一个Visual Studio错误还是我做错了什么?
答案 0 :(得分:1)
版本1和版本2执行未定义的行为,Slot::operator()
Slot1<int>
的{{1}}调用A1
等于int&
Signal1<int>
,然后{{1}自己到static_cast
。它不是Slot1<int&>
,所以演员会生成一个错误的引用,然后你可以使用它,并且无论发生什么都会发生繁荣。
请检查您的类型并停止基于参数类型的显式投射,这是非常不安全和令人讨厌的追踪。
我对你的代码中的Slot1<int&>
的乱七八糟的东西不感兴趣,以确定版本3是否发生类似的错误。你的设计从根本上说是不安全的,因为相对无害的参数在参数传递给{{1}时会发生变化导致未定义的行为。您不应该隐式地将推导出的参数带到#ifdef
并使用它将Slot
的类型转换为派生类型。
尊重类型铸造。
以下是代码在案例1中导致的未定义行为的详细分类:
operator()
呼叫
this
调用
signal.raise(7);
此处Signal1<int>::raise(int)
是void raise(int a1)
{
(*mSlots[0])(a1);
}
类型的左值。所以这个叫
Slot :: operator()(int&amp; a1)const
因为这就是转发引用的工作方式 - a1
将int
推断为T&&
int&
。然后身体包含
T
将int&
转换为对static_cast<const Slot1<int&>&>(*this)(std::forward<int&>(a1));
的引用,*this
是与所讨论的对象无关的类。当您尝试与其进行交互时会产生未定义的行为。
正如我所说,修复此问题是可能的,但您的基本问题是Slot1<int&>
中的类型参数的推断不是一种合适的方式来确定哪种子类型Slot::operator()
您希望将Slot
投射到其中。传递给*this
的表达式的类型通常在通话时不明显,也不在演绎点。如果你没有这个类型完全正确,结果是未定义的行为。通常情况下,这似乎可以起作用,直到发生一些完全不相关的变化并且它会中断。
当从to-derived转换为from-base时,你必须必须小心,明确,并记录你在每一步所做的事情。
如果有人使用unsigned int调用您的operator()
(实际上是Slot
),则会定义未定义的行为。 size_t,未定义的行为。 unsigned char,未定义的行为。 uint16_t,未定义的行为。向unsigned int添加long long,用结果调用,未定义的行为。一种隐式转换为int的类型,未定义的行为。
没有类型安全的多态性很少是一个好主意。
除此之外,您的代码中没有充分理由这样做。 Slot1<int>
可以实现Signal1<A1>
而非connect
获取Signal
- 毕竟,您拥有类型 - 并存储Slot1<A1>
数组而不是Slot1<A1>
的数组。鉴于 Slot
以后不会导致未定义行为的唯一类型是Slot
,为什么要存储错误的类型?