这篇传奇的C ++委托文章可以轻松转换为C ++ 11,而无需原始的花哨的预处理器魔术。我想知道我是否正确掌握了所有必要的C ++ 11细微差别。

#pragma once
#ifndef DELEGATE_HPP
# define DELEGATE_HPP

#include <cassert>

#include <memory>

#include <new>

#include <type_traits>

#include <utility>

template <typename T> class delegate;

template<class R, class ...A>
class delegate<R (A...)>
{
  using stub_ptr_type = R (*)(void*, A&&...);

  delegate(void* const o, stub_ptr_type const m) noexcept :
    object_ptr_(o),
    stub_ptr_(m)
  {
  }

public:
  delegate() = default;

  delegate(delegate const&) = default;

  delegate(delegate&&) = default;

  delegate(::std::nullptr_t const) noexcept : delegate() { }

  template <class C, typename =
    typename ::std::enable_if< ::std::is_class<C>{}>::type>
  explicit delegate(C const* const o) noexcept :
    object_ptr_(const_cast<C*>(o))
  {
  }

  template <class C, typename =
    typename ::std::enable_if< ::std::is_class<C>{}>::type>
  explicit delegate(C const& o) noexcept :
    object_ptr_(const_cast<C*>(&o))
  {
  }

  template <class C>
  delegate(C* const object_ptr, R (C::* const method_ptr)(A...))
  {
    *this = from(object_ptr, method_ptr);
  }

  template <class C>
  delegate(C* const object_ptr, R (C::* const method_ptr)(A...) const)
  {
    *this = from(object_ptr, method_ptr);
  }

  template <class C>
  delegate(C& object, R (C::* const method_ptr)(A...))
  {
    *this = from(object, method_ptr);
  }

  template <class C>
  delegate(C const& object, R (C::* const method_ptr)(A...) const)
  {
    *this = from(object, method_ptr);
  }

  template <
    typename T,
    typename = typename ::std::enable_if<
      !::std::is_same<delegate, typename ::std::decay<T>::type>{}
    >::type
  >
  delegate(T&& f) :
    store_(operator new(sizeof(typename ::std::decay<T>::type)),
      functor_deleter<typename ::std::decay<T>::type>),
    store_size_(sizeof(typename ::std::decay<T>::type))
  {
    using functor_type = typename ::std::decay<T>::type;

    new (store_.get()) functor_type(::std::forward<T>(f));

    object_ptr_ = store_.get();

    stub_ptr_ = functor_stub<functor_type>;

    deleter_ = deleter_stub<functor_type>;
  }

  delegate& operator=(delegate const&) = default;

  delegate& operator=(delegate&&) = default;

  template <class C>
  delegate& operator=(R (C::* const rhs)(A...))
  {
    return *this = from(static_cast<C*>(object_ptr_), rhs);
  }

  template <class C>
  delegate& operator=(R (C::* const rhs)(A...) const)
  {
    return *this = from(static_cast<C const*>(object_ptr_), rhs);
  }

  template <
    typename T,
    typename = typename ::std::enable_if<
      !::std::is_same<delegate, typename ::std::decay<T>::type>{}
    >::type
  >
  delegate& operator=(T&& f)
  {
    using functor_type = typename ::std::decay<T>::type;

    if ((sizeof(functor_type) > store_size_) || !store_.unique())
    {
      store_.reset(operator new(sizeof(functor_type)),
        functor_deleter<functor_type>);

      store_size_ = sizeof(functor_type);
    }
    else
    {
      deleter_(store_.get());
    }

    new (store_.get()) functor_type(::std::forward<T>(f));

    object_ptr_ = store_.get();

    stub_ptr_ = functor_stub<functor_type>;

    deleter_ = deleter_stub<functor_type>;

    return *this;
  }

  template <R (* const function_ptr)(A...)>
  static delegate from() noexcept
  {
    return { nullptr, function_stub<function_ptr> };
  }

  template <class C, R (C::* const method_ptr)(A...)>
  static delegate from(C* const object_ptr) noexcept
  {
    return { object_ptr, method_stub<C, method_ptr> };
  }

  template <class C, R (C::* const method_ptr)(A...) const>
  static delegate from(C const* const object_ptr) noexcept
  {
    return { const_cast<C*>(object_ptr), const_method_stub<C, method_ptr> };
  }

  template <class C, R (C::* const method_ptr)(A...)>
  static delegate from(C& object) noexcept
  {
    return { &object, method_stub<C, method_ptr> };
  }

  template <class C, R (C::* const method_ptr)(A...) const>
  static delegate from(C const& object) noexcept
  {
    return { const_cast<C*>(&object), const_method_stub<C, method_ptr> };
  }

  template <typename T>
  static delegate from(T&& f)
  {
    return ::std::forward<T>(f);
  }

  static delegate from(R (* const function_ptr)(A...))
  {
    return function_ptr;
  }

  template <class C>
  using member_pair =
    ::std::pair<C* const, R (C::* const)(A...)>;

  template <class C>
  using const_member_pair =
    ::std::pair<C const* const, R (C::* const)(A...) const>;

  template <class C>
  static delegate from(C* const object_ptr,
    R (C::* const method_ptr)(A...))
  {
    return member_pair<C>(object_ptr, method_ptr);
  }

  template <class C>
  static delegate from(C const* const object_ptr,
    R (C::* const method_ptr)(A...) const)
  {
    return const_member_pair<C>(object_ptr, method_ptr);
  }

  template <class C>
  static delegate from(C& object, R (C::* const method_ptr)(A...))
  {
    return member_pair<C>(&object, method_ptr);
  }

  template <class C>
  static delegate from(C const& object,
    R (C::* const method_ptr)(A...) const)
  {
    return const_member_pair<C>(&object, method_ptr);
  }

  void reset() { stub_ptr_ = nullptr; store_.reset(); }

  void reset_stub() noexcept { stub_ptr_ = nullptr; }

  void swap(delegate& other) noexcept { ::std::swap(*this, other); }

  bool operator==(delegate const& rhs) const noexcept
  {
    return (object_ptr_ == rhs.object_ptr_) && (stub_ptr_ == rhs.stub_ptr_);
  }

  bool operator!=(delegate const& rhs) const noexcept
  {
    return !operator==(rhs);
  }

  bool operator<(delegate const& rhs) const noexcept
  {
    return (object_ptr_ < rhs.object_ptr_) ||
      ((object_ptr_ == rhs.object_ptr_) && (stub_ptr_ < rhs.stub_ptr_));
  }

  bool operator==(::std::nullptr_t const) const noexcept
  {
    return !stub_ptr_;
  }

  bool operator!=(::std::nullptr_t const) const noexcept
  {
    return stub_ptr_;
  }

  explicit operator bool() const noexcept { return stub_ptr_; }

  R operator()(A... args) const
  {
//  assert(stub_ptr);
    return stub_ptr_(object_ptr_, ::std::forward<A>(args)...);
  }

private:
  friend struct ::std::hash<delegate>;

  using deleter_type = void (*)(void*);

  void* object_ptr_;
  stub_ptr_type stub_ptr_{};

  deleter_type deleter_;

  ::std::shared_ptr<void> store_;
  ::std::size_t store_size_;

  template <class T>
  static void functor_deleter(void* const p)
  {
    static_cast<T*>(p)->~T();

    operator delete(p);
  }

  template <class T>
  static void deleter_stub(void* const p)
  {
    static_cast<T*>(p)->~T();
  }

  template <R (*function_ptr)(A...)>
  static R function_stub(void* const, A&&... args)
  {
    return function_ptr(::std::forward<A>(args)...);
  }

  template <class C, R (C::*method_ptr)(A...)>
  static R method_stub(void* const object_ptr, A&&... args)
  {
    return (static_cast<C*>(object_ptr)->*method_ptr)(
      ::std::forward<A>(args)...);
  }

  template <class C, R (C::*method_ptr)(A...) const>
  static R const_method_stub(void* const object_ptr, A&&... args)
  {
    return (static_cast<C const*>(object_ptr)->*method_ptr)(
      ::std::forward<A>(args)...);
  }

  template <typename>
  struct is_member_pair : std::false_type { };

  template <class C>
  struct is_member_pair< ::std::pair<C* const,
    R (C::* const)(A...)> > : std::true_type
  {
  };

  template <typename>
  struct is_const_member_pair : std::false_type { };

  template <class C>
  struct is_const_member_pair< ::std::pair<C const* const,
    R (C::* const)(A...) const> > : std::true_type
  {
  };

  template <typename T>
  static typename ::std::enable_if<
    !(is_member_pair<T>{} ||
    is_const_member_pair<T>{}),
    R
  >::type
  functor_stub(void* const object_ptr, A&&... args)
  {
    return (*static_cast<T*>(object_ptr))(::std::forward<A>(args)...);
  }

  template <typename T>
  static typename ::std::enable_if<
    is_member_pair<T>{} ||
    is_const_member_pair<T>{},
    R
  >::type
  functor_stub(void* const object_ptr, A&&... args)
  {
    return (static_cast<T*>(object_ptr)->first->*
      static_cast<T*>(object_ptr)->second)(::std::forward<A>(args)...);
  }
};

namespace std
{
  template <typename R, typename ...A>
  struct hash<::delegate<R (A...)> >
  {
    size_t operator()(::delegate<R (A...)> const& d) const noexcept
    {
      auto const seed(hash<void*>()(d.object_ptr_));

      return hash<typename ::delegate<R (A...)>::stub_ptr_type>()(
        d.stub_ptr_) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
  };
}

#endif // DELEGATE_HPP


示例用法:

#include <iostream>

#include "delegate.hpp"

struct A
{
  void foo(int a)
  {
    std::cout << "method got: " << a << std::endl;
  }
};

void foo(int a)
{
  std::cout << "function got: " << a << std::endl;
}

int main(int argc, char* argv[])
{
  auto d1(delegate<void (int)>::from<foo>());

  A a;
  auto d2(delegate<void (int)>::from<A, &A::foo>(&a));
  auto d3(delegate<void (int)>{foo});
  auto d4(delegate<void (int)>(&a, &A::foo));

  d1(1);
  d2(2);
  d3(3);
  d4(4);

  int b(2);

  auto dx(delegate<void ()>(
    [b](){std::cout << "hello world: " << b << std::endl;}));

  dx();

  return 0;
}


评论

有什么理由使用它代替std :: function吗?

@ony通常更快,尤其是在使用指向成员模板参数的指针时。

有什么理由在C ++库/运行时中实现std :: function的实现较慢?同样,还有代码return from([object,method_ptr](A const ... args){return(object。* method_ptr)(args ...);}); }

@ony Benchmark,您将看到。如果您喜欢它,请使用它。代码有问题吗?它不能编译吗?如果是这样,请发布编译错误。代码是C ++ 11。您也可以发表评论作为答案。

上次我检查std :: function的速度与不可能快的委托类相比。我通过使用我的库和Apple clang 4.1进行了此基准测试(尽管std :: function的基准测试实际上不存在,但我敢肯定,您可以使用自己的代码甚至是我的代码对其进行测试)。

#1 楼

看起来大体上还可以。只是一些nitpicks


默认构造函数可能只是delegate() = default;,或者初始化所有成员...或者在没有意义的情况下将其完全删除。
复制构造函数应使用初始化,而不是赋值。
swap应该返回void

赋值应该传递值:

delegate& operator=(delegate rhs) { rhs.swap(*this); return *this; }



调用应使用任意参数并转发:

template <typename ...B>
R operator()(B &&... b)
{
    return (*stub_ptr)(object_ptr, std::forward<B>(b)...);
}


实际上,您应该将enable_if加上各种版本的is_constructible<A, B>...添加到运算符,以免造成不必要的重载。



评论


\ $ \ begingroup \ $
感谢您的评论!我认为转发仍然不稳定,因为静态函数存根仍会复制其参数。这个可以解决吗?
\ $ \ endgroup \ $
–user1095108
2012年8月17日19:01

\ $ \ begingroup \ $
@ user1095108:我认为调用是自动的d =委托:: from_function (); -没有争论的余地了吗?
\ $ \ endgroup \ $
– Kerrek SB
2012年8月17日在19:11



\ $ \ begingroup \ $
lambdas呢?我们可以把他们变成代表吗?
\ $ \ endgroup \ $
–沃伦·塞纳河
2012年10月30日,0:16

\ $ \ begingroup \ $
@WarrenSeine我添加了lambda支持。
\ $ \ endgroup \ $
–user1095108
13年2月16日在23:20

\ $ \ begingroup \ $
@ doug65536避免必须具有const T&和T &&版本的分配;按值传递将自动为rhs选择移动构造或复制构造。
\ $ \ endgroup \ $
–本·海默斯(Ben Hymers)
2013年9月4日13:42

#2 楼

我喜欢这个!运行一些快速基准测试:

int x{0}; 
for(int xx = 0; xx < 5; ++xx)
{
    startBenchmark();
    {
        std::function<int(int)> t2 = [&x](int i){ return i + x; };
        std::function<void(int)> t1 = [&x, &t2](int i){ x = t2(i); };
        for(int i = 0; i < 1000000000; ++i) t1(i);
    } lo << lt("std::func") << endBenchmark() << endl;

    startBenchmark();
    {
        delegate<int(int)> t2 = [&x](int i){ return i + x; };
        delegate<void(int)> t1 = [&x, &t2](int i){ x = t2(i); };
        for(int i = 0; i < 1000000000; ++i) t1(i);
    } lo << lt("ssvu::fastfunc") << endBenchmark() << endl;
}


结果:

[std::func]                           3278 ms
[ssvu::fastfunc]                      2147 ms
[std::func]                           3264 ms
[ssvu::fastfunc]                      2126 ms
[std::func]                           3302 ms
[ssvu::fastfunc]                      2182 ms
[std::func]                           3306 ms
[ssvu::fastfunc]                      2135 ms
...


您的delegate类始终快1秒钟,即使在这个幼稚的基准测试中也是如此。


实际审查:

我测试了所有项目中将所有std::function<R(A...)>更改为delegate<R(A...)>的情况。

delegate代码稍作调整后,一切都会按预期工作!

在我的某些代码中,我曾经这样做:
br />使用delegate而不是std::function时,此代码中断。我通过将这些行添加到您的delegate类中来进行修复:

void a(std::function<void()> mMaybe = nullptr)
{
    // do something
    if(mMaybe != nullptr) mMaybe();
}


现在,以上代码可以编译并正常工作。通过这种小的调整,delegate类可能是std::function的快速替代品。

评论


\ $ \ begingroup \ $
感谢您的贡献,我将您的代码添加到委托类中。
\ $ \ endgroup \ $
–user1095108
2013年9月4日在12:47

\ $ \ begingroup \ $
也请从存储库中尝试staticdelegate.hpp。
\ $ \ endgroup \ $
–user1095108
2013年9月5日23:38

\ $ \ begingroup \ $
@ user1095108:pastebin.com/YwEaLhhF
\ $ \ endgroup \ $
–罗密欧(Vittorio Romeo)
2013年9月6日,0:10

\ $ \ begingroup \ $
注意:rawfunc是委托 t1 =&rawFunc ;,而rawFunc是一个简单的全局函数。注意2:如果您设法优化rawfunc,则可以使用Don's fastdelegate(将其绑定为全局函数)的相同概念来优化mem rawfunc。
\ $ \ endgroup \ $
–罗密欧(Vittorio Romeo)
2013年9月6日下午0:11

\ $ \ begingroup \ $
注3:如果您想了解Don的fastdelegate实现如何处理原始的全局函数,我会对其进行大幅度的清理
\ $ \ endgroup \ $
–罗密欧(Vittorio Romeo)
2013年9月6日0:14在

#3 楼

有问题的结果:

[std::func]                           31855 ms
[ssvu::fastfunc]                      123848 ms


基准代码:

int x{0};
for(int xx = 0; xx < 5; ++xx)
{
    startBenchmark();
    {
        for(int i = 0; i < 1000000000; ++i)
        {
            std::function<int(int)> t2 = t2impl;
            std::function<void(int)> t1 = [&x, &t2](int i){ x = t2(i); };
            t1(i);
        }
    } lo << lt("std::func") << endBenchmark() << endl;
    startBenchmark();
    {
        for(int i = 0; i < 1000000000; ++i)
        {
            FastFunc<int(int)> t2 = t2impl;
            FastFunc<void(int)> t1 = [&x, &t2](int i){ x = t2(i); };
            t1(i);
        }
    } lo << lt("ssvu::fastfunc") << endBenchmark() << endl;
}


std::function在此基准上表现更好。知道为什么吗?

评论


\ $ \ begingroup \ $
它可能必须对函子进行初始化。 :: std :: function应该使用小函子来优化初始化。我没有优化该用例。尝试将FastFunc对象的定义移出循环,然后只需将std :: move lambda对象移入其中即可。
\ $ \ endgroup \ $
–user1095108
2013年9月4日13:12



\ $ \ begingroup \ $
我对valgrind进行了分析(仅针对委托类):似乎真正的瓶颈是动态内存分配i.imgur.com/k01y9fA.png
\ $ \ endgroup \ $
–罗密欧(Vittorio Romeo)
2013年9月4日13:15

\ $ \ begingroup \ $
尝试仅在循环中分配:t2 = std :: move(t2impl); t1 = std :: move([&x,&t2](int i){x = t2(i);};);
\ $ \ endgroup \ $
–user1095108
2013年9月4日13:18

\ $ \ begingroup \ $
@ user1095108没有骰子-结果与原始基准代码相同(委托比std :: function慢很多)
\ $ \ endgroup \ $
–罗密欧(Vittorio Romeo)
2013年9月4日13:21



\ $ \ begingroup \ $
如果这是代码中的常见用例,则需要查找其他委托或为此代码优化此代码(即,可以使用一个小的char []数组来存储函子,而不是对其进行新建)。 -注意对齐问题),或者您可以尝试使用Boost.Pool或一些小对象分配器。请注意,原始委托根本不支持函子(lambda对象),但是利用了编译时指针指向函数和指针指向函数成员模板参数的可能性。
\ $ \ endgroup \ $
–user1095108
2013年9月4日13:27