6

一般的なシグナル/スロット システム用に次の実装を作成しました。

template< typename... Args >
class Signal : NonCopyable
{
public:

    typedef std::function< void (Args...) > Delegate;

    void connect(const Delegate& delegate);
    void operator ()(Args&&... args) const;

private:

    std::list< Delegate > _delegates;
};

template< typename... Args >
void Signal< Args... >::connect(const Delegate& delegate)
{
    _delegates.push_front(delegate);
}

template< typename... Args >
void Signal< Args... >::operator ()(Args&&... args) const
{
    for (const Delegate& delegate : _delegates)
        delegate(std::forward< Args >(args)...);
}

その後、次の単純なケースを使用してクラスをテストしました。

Signal< int > signal;

// Case 1
signal(0);

//Case 2
int i(0);
signal(i);

ケース 1 は問題なくコンパイルされます。一方、ケース 2 では、GCC 4.7.2 で次のエラーが生成されます。

/home/pmjobin/Workspace/main.cpp:1196:10: error: cannot bind ‘int’ lvalue to ‘int&&’
/home/pmjobin/Workspace/main.cpp:82:6: error:   initializing argument 1 of ‘void Signal<Args>::operator()(Args&& ...) const [with Args = {int}]’

この問題は完全転送と関係があることを理解しています(および後者に対する私の誤解)。ただし、私は std::make_shared() と std::make_tuple() の実装からインスピレーションを得ており、可変引数をデリゲートに転送する方法に違いは見られません。唯一の注目すべき違いは、make_shared() と make_tuple() の両方が、上記の Signal 実装などのクラス テンプレートではなく、関数テンプレートであることです。

- 編集 -

さまざまなコメントに応えて、前述の問題の影響を受けない新しいバージョンの Signal クラス実装を以下に示します。さらに、connect 関数によって返される不透明なトークンを使用してデリゲートを切断できるようになりました。結果は、他の実装 (boost::signal など) ほど柔軟で強力ではないかもしれませんが、少なくとも、シンプルで軽量であるという利点があります。

template< typename Signature >
class Signal : NonCopyable
{
public:

    typedef std::function< Signature > Delegate;

    class DisconnectionToken
    {
        DisconnectionToken(typename std::list< Delegate >::iterator it)
            : _it(it)
        {}

        typename std::list< Delegate >::iterator _it;

        friend class Signal;
    };

    DisconnectionToken connect(const Delegate& delegate);
    void disconnect(DisconnectionToken& token);

    template< typename... Args >
    void operator ()(Args&&... args) const;

private:

    std::list< Delegate > _delegates;
};

template< typename Signature >
typename Signal< Signature >::DisconnectionToken Signal< Signature >::connect(const Delegate& delegate)
{
    _delegates.push_front(delegate);
    return DisconnectionToken(_delegates.begin());
}

template< typename Signature >
void Signal< Signature >::disconnect(DisconnectionToken& token)
{
    if (token._it != _delegates.end())
    {
        _delegates.erase(token._it);
        token._it = _delegates.end();
    }
}

template< typename Signature >
template< typename... Args >
void Signal< Signature >::operator ()(Args&&... args) const
{
    for (const Delegate& delegate : _delegates)
        delegate(std::forward< Args >(args)...);
}
4

1 に答える 1

5

問題は、テンプレート パラメーターを .xml として明示的に指定していることですint。Nawazが述べたように、Args&&...展開されint&&、左辺値を右辺値参照にバインドできません。

完全転送が機能する理由は、(たとえば) テンプレート引数を指定せずに関数を呼び出すと、それらが または のいずれかに推定され&&&その後参照が崩壊するためです (それが何であるかわからない場合は、参照の崩壊について読んでください)。ただし、明示的に指定するため、参照の崩壊を禁止し、すべてを台無しにします

あなたができることの1つはoperator()、テンプレート引数の独自のリストを与えることです:

template<typename... Args2>
void operator ()(Args2&&... args) const;

...

template< typename... Args >
template< typename... Args2 >
void Signal< Args... >::operator ()(Args2&&... args) const
{
    for (const Delegate& delegate : _delegates)
        delegate(std::forward< Args2 >(args)...);
}

このようにして、参照の折りたたみを処理させることができます。

于 2013-01-23T05:54:32.337 に答える