Невозможно быстрый делегат в C ++ 11

Эта легендарная статья делегата 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;
}
29 голосов | спросил 98 revs, 2 users 100%
user1095108
1 Jam1000000amThu, 01 Jan 1970 03:00:00 +030070 1970, 03:00:00

3 ответа


15

Он выглядит по большому счету ОК. Только некоторые nitpicks

  • конструктор по умолчанию может быть просто delegate() = default; или иначе инициализировать членов all ... или удалить его полностью, если это не имеет смысла .

  • конструктор копирования должен использовать инициализацию, а не назначение.

  • 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>... для оператора, чтобы вы не создавали невозможных перегрузок.

ответил Kerrek SB 17 PM00000090000002231 2012, 21:02:22
4

Мне это нравится! Ранние быстрые тесты:

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 все работает , как ожидалось!

В некоторых из моего кода я использовал это:

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

Этот код сломался при использовании delegate вместо std::function. Я исправил это, добавив эти строки в класс delegate:

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

Теперь приведенный выше код компилируется и работает правильно. С этой небольшой настройкой класс delegate является , возможно, более быстрой заменой для std::function.

ответил Kerrek SB 17 PM00000090000002231 2012, 21:02:22
0

Проблемные результаты:

[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 выполнил способ лучше в этом тесте. Любая идея почему?

ответил Kerrek SB 17 PM00000090000002231 2012, 21:02:22

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132