为什么没有std :: function能够完美转发?

时间:2015-08-07 19:28:06

标签: c++ visual-studio-2010 c++11

注意:

  • 我提前道歉,但我无法进一步减少/简化代码(即我所有较小的测试都无法重现此问题)
  • 使用Visual Studio 2010 Premium编译代码版本1但无法正确执行
  • 版本2& 3个代码使用Visual Studio 2010 Premium
  • 成功编译并执行
  • 所有版本的代码使用ideone.com编译并成功执行(C ++ 14)

版本1输出(Visual Studio 2010)

Signal1<A1>::raise(7)
Slot::operator()(7)
Slot1<A1>::operator()(7)
Handling new value: 3931764 // This value changes on each execution

版本2输出(Visual Studio 2010)

Signal1<A1>::raise(7)
Slot::operator()(7)
Slot1<A1>::operator()(7)
Handling new value: 7

版本3输出(Visual Studio 2010)

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错误还是我做错了什么?

1 个答案:

答案 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 因为这就是转发引用的工作方式 - a1int推断为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 ,为什么要存储错误的类型?