9 Haziran 2020 Salı

std::function

Giriş
Şu satırı dahil ederiz
#include <functional>
std::functon'a alternatif olabilecek seçenekler şöyle
1. Function Pointer
2. Function Template
3 function_view - STL değil. Kodu şöyle

std::function'a Atanabilecek Nesneler
Açıklama şöyle
There are different kinds of callable's, namely functions and functors. std::function was designed to not mater which of those you give to it
Açıklaması şöyle
Can hold a functor, member function pointer, function pointer or lambda
Örnek
Hepsinin kullanımını görmek için şöyle yaparız
class Foo {};
class Bar {};
class FunctorEx
{
public:
  bool operator()(Bar const&, Foo const&)
  {
    return true;
  }
} FunctorExInst;

class MemFunction
{
public:
  bool MemFunctionEx(Bar const&, Foo const&)
  {
    return true;
  }
} MemFunctionInst;

bool FunctionEx(Bar const&, Foo const&)
{
  return true;
}

int main()
{
  auto LambdaEx = [] (Bar const&, Foo const&) -> bool
  {
    return true;
  };

  std::function<bool(Bar const&, Foo const&)> exFunctionVar;
  exFunctionVar = std::bind(&MemFunction::MemFunctionEx, &MemFunctionInst,
     std::placeholders::_1, std::placeholders::_2);
  exFunctionVar = FunctorExInst;
  exFunctionVar = FunctionEx;
  exFunctionVar = LambdaEx;
}
Tanımlama - C++98
Şeklen şöyledir.
std::function<R(A, B, C, ...)>
Kodda ise şöyledir
template<class R, class... ArgTypes> 
class function<R(ArgTypes...)> { 
  template<class F> function(F f); 
};
Önce return type, sonra parantez içinde function'a geçilen parametreler belirtilir.
std::function<void(const char *)
Bir başka örnek
std::function<int (int)> myFunc;
Eğer parametreleri belirtmezsek istediğimiz sayıda ve tipte parametre geçebiliriz. Şöyle yaparız.
std::function<void()>> myFunc;
Bu yöntemi bir projede callback çağırmak için kullandık. Callback'leri şöyle bağladık.
setCallback (std::bind (foo,1));
setCallback (std::bind (foo,1,2));

Tanımlama - C++11
Şöyle yaparız.
using myFunc = std::function<int(const Foo&)>;
Metodlar
Contructor - F
İmzası şöyle. F nesnesinin kopyasını saklar.
template< class F > 
function( F f );
Açıklaması şöyle.
Requires: F shall be CopyConstructible. f shall be Callable for argument types ArgTypes and return type R. [...]
Örnek - lambda
std::function ve Lambda yazısına taşıdım.

Contructor - Allocator + F
İmzası şöyle.
template <class F, class A> function(allocator_arg_t, const A& a, F f);
std::function ile allocator kullanılması C++11 ile eklendi. Daha sonra C++17 ile çıkarıldı. Açıklaması şöyle.
Unfortunately, allocators for std::function has been dropped in C++17.

Now the accepted solution to avoid dynamic allocations inside std::function is to use lambdas instead of std::bind. That does work, at least in GCC - it has enough static space to store the lambda in your case, but not enough space to store the binder object.
Örnek
C++17 örneğinde allocator kullanılmıyor. std::function kendi içinde bellek ayırıyor. lambda ve std::bind farklı miktarlarda bellek gerektirirler. Bunu görmek için şöyle yaparız.
MyCallBack cb;
std::cout<< sizeof(std::bind(&MyCallBack::Fire, &cb)) << "\n";
auto a = [&] { cb.Fire(); };
std::cout << sizeof(a);
Çıktı olarak 24 ve 8 alırız. Yani std::bind 24 byte veri tüketirken, lambda 8 byte veri tüketir. 
Dolayısıyla mümkünse şöyle yapmak gerekir.
std::function<void()> func = [&cb]{ cb.Fire(); };
    // sizeof lambda is sizeof(MyCallBack*), which is small enough
Açıklaması şöyle
As a general rule, with most implementations, and with a lambda which captures only a single pointer (or a reference), you will avoid dynamic allocations inside std::function with this technique (it is also generally better approach as other answer suggests).

Keep in mind, for that to work you need guarantee that this lambda will outlive the std::function. Obviously, it is not always possible, and sometime you have to capture state by (large) copy. If that happens, there is no way currently to eliminate dynamic allocations in functions, other than tinker with STL yourself (obviously, not recommended in general case, but could be done in some specific cases).
target metodu
std::function nesnesini function pointer'a dönüştürür. Şu kod derlenmez.
std::function<int(int)> func = [](int x){return x;};
 int(*Fptr)(int) = &func; //error
Ancak şöyle yapabiliriz. Burada T kullandım ancak yerine function imzasını yazmak yeterli. Eğer imzalar uyumlu değilse null döner.
T *ptr_func = func.target<T>();
Gerçek koda bakalım. Elimizde şöyle bir function pointer tipi olsun olsun.
typedef void (*myhandlertype)(int);
Bu tiple uyumlu bir std::function olsun.
std::function<void (int)> func = ...;
Şöyle çeviririz.
myhandlertype* handler = func.target<void (int)>();
!operator
function nesnesinin dolu olup olmadığı anlaşılabilir. Şöyle yaparız.
std::function<int (int)> myFunc;
m_pInit = ...;
std::cout << !myFunc << std::endl;   // True
==operator
Açıklaması şöyle
Compares a std::function with a null pointer. Empty functions (that is, functions without a callable target) compare equal, non-empty functions compare non-equal.
Şöyle yaparız.
std::function<int (int)> myFunc;
if (myFunc == nullptr) {...}

Diğer Notlar
std::function ve kendi virtual metodlarım
Şöyle yapabilirz. std::function doğru virtual metodu çağırır.
class foo
{
public:
  foo()
  {
    this->f = std::bind(&foo::doSomething, this);
  }

private:
  virtual void doSomething(void) { }

private:
   std::function<void(void)> f;
}

class bar : public foo
{
public:
  bar(void) {}

private:
  virtual void doSomething(void) override { }
}
std::function ve strategy örüntüsü
std::function strategy örütüntüsünü gerçekleştirmek için kullanılabilir. Bu durumda interface sınıfını kaybederiz ancak lambda kullanırken belki tercih edilebilir.

std::function ve overhead
st::function nesnesinin herhangi bir function pointer'a göre daha ağır olduğu kesin. En azından heap allocation ve bir virtual function çağrısı yapma ihtimali var. Bence ne kadar ağır olursa olsun, function pointer'dan her durumda daha kullanışlı.

Overload Metodlar
std::function overload edilmiş metodlar ile pek iyi çalışmıyor. Aşağıdaki kod derlenmez.
#include <functional>

void add(int,int){}

class A {};

void add (A, A){}

int main(){
  std::function <void(int, int)> func = add;
}
static_cast ile overload metodu belirtmek gerekir.
std::function <void(int, int)> func = static_cast<void(*)(int, int)> (add);
std::function'ı metodlara parametre geçmek
Klasik nesne gibi value veya reference olarak geçilebilir.
void foo(std::function<...> f)
{
    f(...);
}
veya
void foo(const std::function<...>& f)
{
    f(...);
}
Bence const& olanı tercih ediyorum.
Eğer default boş bir değer std::function tanımlamak istersek
1. Şöyle yapabiliriz.
void func(const std::function<void()>& f = {}) {
    if(f) f();
}
2. Şöyle yapabiliriz.
void func(const std::function<void()>& f = nullptr)
{
    if(f)
        f();
}
3. Şöyle yapabiliriz.
void func(const std::function<void()>& f = std::function<void()>()) {
  try {
    f();
  }
  catch(std::bad_function_call e) {
  }
}
3. örnekte exception yakalanmasının sebebi şu. Eğer 2. örnekteki gibi if kontrolü yapsaydık exception yakalamaya gerek kalmazdı.
§ 20.8.11.1 Class bad_function_call [func.wrap.badcall]
1/ An exception of type bad_function_call is thrown by function::operator() (20.8.11.2.4) when the function wrapper object has no target.
std::function ve variadic template
std::function variadic template şeklinde kullanılabiliyor. Ortaya oldukça karışık kodlar çıkıyor. Bazı örnekler burada.
Örnek
Şöyle yaparız.
template <typename ... Args>
void run_callback(std::function<void(Args...)> const & func, Args ... as) {
  try {
    func(as...);
  } catch(const std::exception& ex) {
    print_callback_error(ex.what());
  } catch(const std::string& ex) {
    print_callback_error(ex.c_str());
  } catch(...) {
    print_callback_error();
  }
}
std::function ve signal
Şöyle tanımlarız.
template<class FuncType>
class MulticastFunction {
private:
  vector<std::function<FuncType>> targets;
public:
  void operator()() {
    for(auto& target : this->targets) {
      target();
    }
  }
  void addTarget(FuncType& target) {
    this->targets->push_back( target );
  }
}
Şöyle çağırırız.
MulticastFunction<void()> mc;
mc.addTarget( foo );
mc.addTarget( bar );
mc();

1 yorum:

  1. Siteyi yeni keşfettim. Yazılarınızın kesilmemesi dileğiyle...

    YanıtlaSil