完美的转发会导致错误,我不明白

时间:2014-04-27 10:15:15

标签: c++11 templates reference

这是this question的后续行动,鼓励我在可变参数模板运算符()(...)实现中使用完美转发。这是我的观察者模式,我想用它来调用具有可变参数的自由函数和成员函数:

#ifndef _SIGNALS_H_
#define _SIGNALS_H_

#include <utility>

/** Interface for delegates with a specific set of arguments **/
template<typename... args>
class AbstractDelegate
{
  public:
    virtual void operator()(args&&...) const = 0;
    virtual ~AbstractDelegate() {}
};

/** Concrete function delegate that discards the function's return value **/
template<typename ReturnType, typename... args>
class FnDelegate : public AbstractDelegate<args...>
{
  public:
    /** member function typedef **/
    using Fn = ReturnType(*)(args...);

    /** constructor **/
    FnDelegate(Fn fn)
      : fn_{fn}
    {
    }

    /** call operator that calls the stored function **/
    void operator()(args&&... a) const override
    {
      (*fn_)(std::forward<args>(a)...);
    }

  private:
    /** function pointer **/
    const Fn fn_;
};

/** forward declaration **/
template<typename... args>
class Connection;

/** Signal class that can be connected to**/
template<typename... args>
class Signal
{
  public:
    /** connection pointer typedef **/
    typedef Connection<args...>* connection_p;

    /** constructor **/
    Signal()
      : connections_(NULL),
      blocked_(false)
      {
      }

    /** call operator that notifes all connections associated with this Signal.
      The most recently associated connection will be notified first **/
    void operator()(args&&... a) const
    {
      // only notify connections if this signal is not blocked
      if (!blocked())
      {
        auto c = connections_;
        while(c != NULL)
        {
          (*c)(std::forward<args>(a)...);
          c = c->next();
        }
      }
    }

    /** connect to this signal **/
    void connect(connection_p p)
    {
      p->next_ = connections_;
      connections_ = p;
      p->signal_ = this;
    }

    /** disconnect from this signal.
      Invalidates the connection's signal pointer
      and removes the connection from the list **/
    void disconnect(connection_p conn)
    {
      // find connection and remove it from the list
      connection_p c = connections_;
      if (c == conn)
      {
        connections_ = connections_->next();
        conn->next_ = NULL;
        conn->signal_ = NULL;
        return;
      }
      while(c != NULL)
      {
        if (c->next() == conn)
        {
          c->next_ = conn->next();
          conn->next_ = NULL;
          conn->signal_ = NULL;
          return;
        }
        c = c->next();
      }
    }

    /** block events from this signal **/
    void block()
    {
      blocked_ = true;
    }

    /** unblock events from this signal **/
    void unblock()
    {
      blocked_ = false;
    }

    /** is this signal blocked? **/
    bool blocked() const
    {
      return blocked_;
    }

    /** destructor. disconnects all connections **/
    ~Signal()
    {
      connection_p p = connections_;
      while(p != NULL)
      {
        connection_p n = p->next();
        disconnect(p);
        p = n;
      }
    }

    connection_p connections() const {return connections_;}

  private:
    connection_p connections_;
    bool blocked_;
};

/** connection class that can be connected to a signal **/
template<typename... args>
class Connection
{
  public:
    /** template constructor for static member functions and free functions.
      allocates a new delegate on the heap **/
    template<typename ReturnType>
    Connection(Signal<args...>& signal, ReturnType (*Fn)(args...))
      : delegate_(new FnDelegate<ReturnType, args...>(Fn)),
      signal_(NULL),
      next_(NULL),
      blocked_(false)
    {
      signal.connect(this);
    }

    /** get reference to this connection's delegate **/
    AbstractDelegate<args...>& delegate() const
    {
      return *delegate_;
    }

    /** call this connection's delegate if not blocked **/
    void operator()(args&&... a) const
    {
      if (!blocked())
      {
        delegate()(std::forward<args>(a)...);
      }
    }

    /** get pointer to next connection in the signal's list **/
    Connection* next() const
    {
      return next_;
    }

    /** is this connection connected to a valid signal? **/
    bool connected() const
    {
      return (signal_ != NULL);
    }

    /** block events for this connection **/
    void block()
    {
      blocked_ = true;
    }

    /** unblock events for this connection **/
    void unblock()
    {
      blocked_ = false;
    }

    /** is this connection blocked? **/
    bool blocked() const
    {
      return blocked_;
    }

    /** desctructor. If the signal is still alive, disconnects from it **/
    ~Connection()
    {
      if (signal_ != NULL)
      {
        signal_->disconnect(this);
      }
      delete delegate_;
    }

    const Signal<args...>* signal() const {return signal_;}

    friend class Signal<args...>;
  private:
    AbstractDelegate<args...>* delegate_;
    Signal<args...>* signal_;
    Connection* next_;
    bool blocked_;
};

/** free connect function: creates a connection (static member or free function) on the heap
  that can be used anonymously **/
template<typename ReturnType, typename... args>
Connection<args...>* connect(Signal<args...>& signal, ReturnType (*fn)(args...))
{
  return new Connection<args...>(signal, fn);
}

#endif // _SIGNALS_H_

我正试图以这些方式使用它:

Signal<int> sig;

void print(int i)
{
  std::cout << "print(" << i << ")" << std::endl;
}

int get(int i)
{
  return i;
}

int main()
{
  connect(sig, print);
  sig(3);
  int i = 4;
  sig(i); // <-- here I get an error
  sig(get(5));
}

我得到的错误是

main.cpp: In function ‘int main()’:
main.cpp:21:10: error: cannot bind ‘int’ lvalue to ‘int&&’
 sig(i);
      ^
In file included from main.cpp:2:0:
main.h:89:10: error:   initializing argument 1 of ‘void Signal<args>::operator()(args&& ...) const [with args = {int}]’
 void operator()(args&&... a) const
      ^

当我在任何地方使用const int&时,错误消失,即Signal<const int&> sigvoid print(const int&),但我不明白为什么。此外,在“标志”信号的情况下传递const bool&会感到尴尬。

你能否提出一个可以在这里提供更多灵活性的修复方法?

1 个答案:

答案 0 :(得分:7)

您的代码审核失败了。通用引用技术并不适用于virtual抽象接口。

请勿在纯虚拟界面的签名中使用Args&&,使用Args...。在实现中,最后一次(或唯一一次)使用arg使用std::forward<Args>(args)...有条件地移动当且仅当参数类型是&&右值引用或文字时。

使用Args&&...时,您的函数采用左值引用或右值引用。在许多情况下,您都不希望显式传递参数类型。在类型推导上下文中Args&&...而是自动检测“最佳”和“最佳”。匹配您的参数类型,但您没有推断出这些类型。

当您在类型推导的上下文中使用Args...时,推导出的类型始终是文字。通常这是次优的。但是,如果您指定类型,则没有问题。

使用std::forward有条件地移动变量。如果传入的类型是右值引用或者是文字,它会移动。当完美转发通用引用时,以及我在上面描述的用例中,这恰好就是做正确的事情。

template<typename... args>
class AbstractDelegate
{
  public:
    virtual void operator()(args...) const = 0;
    virtual ~AbstractDelegate() {}
};

/** Concrete function delegate that discards the function's return value **/
template<typename ReturnType, typename... args>
class FnDelegate : public AbstractDelegate<args...>
{
  public:
    /** member function typedef **/
    using Fn = ReturnType(*)(args...);

    /** constructor **/
    FnDelegate(Fn fn)
      : fn_{fn}
    {
    }

    /** call operator that calls the stored function **/
    void operator()(args... a) const override
    {
      (*fn_)(std::forward<args>(a)...); // last (&only) use of each arg, ok to forward
    }

...

Signal中,您可以删除forward(保留&&,因为它是非virtual界面),或者您可以执行更复杂的操作,但只能{ {1}}关于最后一个信号:

forward

由于// stay args&&... here: void operator()(args&&... a) const { // only notify connections if this signal is not blocked if (!blocked()) { auto c = connections_; while(c) { auto c_next = c->next(); if (c_next) (*c)(a...); else (*c)(std::forward<args>(a)...); // last use, can forward c = c_next; } } } 是一个条件移动,因此大多数情况下只应在forward有效的上下文中使用它。 (当然,不完全是由于移动的条件性质,但模式是有效的。)

由于变量不止一次对move无效,因此对变量的move无效一次。