26 Mart 2020 Perşembe

std::reference_wrapper - lvalue İle Çalışır

Giriş
Şu satırı dahil ederiz. Bu sınıf bir nesneyi veya bir metodu sarmalar.
#include <functional>
Dangling Reference Problemi
Bu sınıf bizi dangling reference probleminden kurtarır. Çünkü rvalue değerlerle çalışmaz.
Örnek
Elimizde şöyle bir kod olsun. Bu kod dangling reference yüzünden hatalıdır.
#include <vector>
#include <iostream>

std::vector<int> someNums()
{
    return {3, 5, 7, 11};
}

class Woop
{
public:
    Woop(const std::vector<int>& nums) : numbers(nums) {}
    void report()
    {
        for (int i : numbers)
            std::cout << i << ' ';
        std::cout << '\n';
    }
private:
    const std::vector<int>& numbers;
};

int main()
{
    Woop woop(someNums());
    woop.report();
}
Düzeltmek için şöyle yaparız
#include <functional>

class Woop
{
public:
    using NumsRef = ::std::reference_wrapper<const std::vector<int>>;
    Woop(NumsRef nums) : numbers_ref{nums} {}
    void report()
    {
        for (int i : numbers_ref.get())
            std::cout << i << ' ';
        std::cout << '\n';
    }
private:
    NumsRef numbers_ref;
};
Böylece şu kod derlenmez
Woop woop{someNums()}; // error
woop.report();
Şöyle yaparız.
auto nums{someNums()};
Woop woop{nums}; // ok
woop.report();
veya şöyle yaparız.
auto nums{someNums()};
Woop woop{::std::ref(nums)}; // even better because explicit
woop.report();
Default Constructor
Mevcut değil. Açıklaması şöyle
std::reference_wrapper is built as a wrapper to a reference, that's it. It must behave like a reference would, or else it wouldn't be a wrapper but something else. There are use-cases for it, which would possibly break if you allow a default value for it. If you need to have a null value, either use something else, like a pointer, or live with the limitations of references.
Copy Constructor
İmzası şöyle.
reference_wrapper(T& ref) noexcept : _ptr(std::addressof(ref)) {}
İmzası şöyle.
template <typename T>
reference_wrapper<T>::reference_wrapper(const reference_wrapper<T>&)
Bir reference_wrapper başka bir reference_wrapper nesnesine kopyalanabilir.
int v = 12;
std::reference_wrapper<int> x(v);
std::reference_wrapper<const int> y(x);  // (1) This works
Move Constructor
Mevcut değil. İmzası şöyle.
reference_wrapper(T&&) = delete;
get metodu
Şöyle yaparız. Sarmalanan nesneyi döner.
int i = 42;
std::list<std::reference_wrapper<int>> lst;

lst.push_back(i);           // we add a "reference" into the list
lst.front().get() = 10;     // we update the list
std::cout << i;             // the initial i was modified!
operator ()
İmzası şöyle. Sarmalanan metodu çağırır.
operator T& () const noexcept { return *_ptr; }
Açıklaması şöyle.
operator() returns std::result_of<T&(ArgTypes&&...), which is return value of the function stored and std::invoke call such function and forwards parameters to it.
Metodun içi şöyle.
template< class... ArgTypes >
typename std::result_of<T&(ArgTypes&&...)>::type
operator() ( ArgTypes&&... args ) const {
  return std::invoke(get(), std::forward<ArgTypes>(args)...);
}
Şöyle yaparız.
std::reference_wrapper<int(int)> ref = foo;
ref(5);

STL Map İçinde Kullanımı
Şöyle yaparız. lvalue değere reference elde ederiz.
std::map<int, std::reference_wrapper<const int>> mb;
const int a = 5;
mb.emplace(0, std::cref(a));
STL Vector İçinde Kullanımı
1.Elimizde şöyle bir vector olsun.
std::vector<double> v(5, 10.3);
Başka bir veri yapısı içindeki elemana referans almak istersek şöyle yaparız. Böylece r vector'ü, v vector'ündeki elemanları değiştirir.
std::vector<std::reference_wrapper<double>> r(v.begin(),v.end());
2. Elimizde bir X içeren vector olsun ve bu vector'ü farklı alanlara göre sıralamak için şöyle yaparız.
std::vector<X> v; // elements of X in some order
std::vector<std::reference_wrapper<X const> > index1(v.begin(), v.end());
std::vector<std::reference_wrapper<X const> > index2(v.begin(), v.end());

// sort the indexes
std::sort(index1.begin(), index1.end(), by_property1);
std::sort(index2.begin(), index2.end(), by_property2);
STL Array İçinde Kullanımı
Şöyle yaparız
int a = 10;
int b = 20;
int c = 20;

using rint = std::reference_wrapper<int>;
std::array<rint,3> x = {a,b,c};

std::cout << "a " << a << std::endl;
x[0].get()=11;
std::cout << "a " << a << std::endl;
Değişkenler İle Kullanımı
Örnek
Şöyle yaparız. Değişkenin adresini elde ederiz.
#include <iostream>
#include <functional>  // for reference_wrapper
using namespace std;
int main()
{
    int x=5, y=7, z=8;
    reference_wrapper<int> arr[] {x,y,z};
    for (auto a:arr)
    cout<<a<<" ";
    return 0;
}
/* OUTPUT :-
5 7 8
*/
Örnek
Şöyle yaparız. Pointer'ın adresini elde ederiz.
#include <functional>

template <typename T>
struct Foo
{
  Foo(T arg) : ptr(arg)
  {
  }
  T ptr;
};

int main()
{
  int* a = new int(6);

  Foo<std::reference_wrapper<int*>> foo1(std::ref(a));
  foo1.ptr[0] = 1;  // ok

  // This also works
  int* b = new int(6);
  Foo<std::reference_wrapper<decltype(b)>> foo2(std::ref(b));
  // and this too
  foo1 = foo2;

  // Or, if you use c++17, even this
  Foo foo3(std::ref(b));
}

Metod İle Kullanımı
Örnek
Şöyle yaparız. Metodun adresini elde ederiz.
int foo(int bar)
{
    return bar + 5;
}

int main()
{
    std::reference_wrapper<int(int)> ref = foo;
    ref(5);
}
Nesne İle Kullanımı
Object slicing'den kurtulup polymorphic davranış için kullanılabilir. Elimizde şöyle bir kod olsun
class base {
public:
  int id;

  virtual void f() const {
    cout << "base f()" << endl;
  }
};

class D1: public base {
public:
  void f() const {
    cout << "D1 f()" << endl;
  }
};

class D2: public base {
public:
  void f() const {
    cout << "D2 f()" << endl;
  }
};
Şöyle yaparız. Böylece priority_queue veri yapısı base& şeklinde çalışır.
priority_queue<std::reference_wrapper<base>, deque<std::reference_wrapper<base>>,
  less<deque<std::reference_wrapper<base>>::value_type> > q;

D1 d1;
D2 d2;

q.push(d1);
q.push(d2);

const auto& b = q.top();
b.get().f();    // D1 f()