19 Ağustos 2019 Pazartesi

std::initializer_list - Heap Kullanmaz

Giriş
Şu satırı dahil ederiz.
#include <initializer_list>
Bu sınıf sadece bir diziye iterator tutar. Verilen diziyi kopyalamaz. Dolayısıyla oldukça ucuz bir sınıftır.  Heap kullanmadığını görmek için şöyle yaparız
#include <string>
#include <iostream>

void* operator new(size_t size)
{
    std::cout << "new overload called" << std::endl;    
    return malloc(size);
}


template <typename T>
void foo(std::initializer_list<T> args)
{
    for (auto&& a : args)
    std::cout << a << std::endl;
}

int main()
{
    foo({2, 3, 2, 6, 7});

    // std::string test_alloc = "some string longer than std::string SSO";
}
Açıklaması şöyle
The thing is, std::initializer_list does not hold the objects inside itself. When you instantiate it, compiler injects some additional code to create a temporary array on the stack and stores pointers to that array inside the initializer_list. For what its worth, an initializer_list is nothing but a struct with two pointers (or a pointer and a size):
Yani elimizde şöyle bir kod olsun
template <class T>
class initializer_list {
private:
  T* begin_;
  T* end_;
public:
  size_t size() const { return end_ - begin_; }
  T const* begin() const { return begin_; }
  T const* end() const { return end_; }

  // ...
};
Şöyle yapalım
foo({2, 3, 4, 5, 6});
Aslında kavramsal olarak şöyle bir  kod üretilir
int __tmp_arr[5] {2, 3, 4, 5, 6};
foo(std::initializer_list{arr, arr + 5});
Kendimiz gerçekleştirsek
Bu sınıfı kendimiz gerçekleştirmek istersek 
Örnek
Bu sınıfı kendimiz gerçekleştirmek istersek şöyle yaparız.
template<class _E>
class my_initializer_list
{
public:
  typedef _E        value_type;
  typedef const _E&     reference;
  typedef const _E&     const_reference;
  typedef size_t        size_type;
  typedef const _E*     iterator;
  typedef const _E*     const_iterator;

private:
  iterator          _M_array;
  size_type         _M_len;

  // The compiler can call a private constructor.
  constexpr my_initializer_list(const_iterator __a, size_type __l)
    : _M_array(__a), _M_len(__l) { }

public:
  constexpr my_initializer_list() noexcept
    : _M_array(0), _M_len(0) { }

  // Number of elements.
  constexpr size_type
  size() const noexcept { return _M_len; }

  // First element.
  constexpr const_iterator
  begin() const noexcept { return _M_array; }

  // One past the last element.
  constexpr const_iterator
  end() const noexcept { return begin() + size(); }
};
std::initializer_list ve constructor
Effective Modern C++ kitabındaki açıklama şöyle. uniform initilization kullanılıyorsa std::initializer_list diğer parametreli constructor'a tercih edilir.
If, however, one or more constructors declare a parameter of type  std::initializer_list, calls using the braced initialization syntax strongly prefer the overloads taking std::initializer_lists. Strongly. If there’s any way for compilers to construe a call using a braced initializer to be to a constructor taking a std::initializer_list, compilers will employ that interpretation.
Örnek
Elimizde iki constructor sunan Foo sınıfı olsun
Foo(std::initializer_list<int>) {
  std::cout << "with initializer list\n";
}
ve
Foo(int) {
  std::cout << "with int\n";
}
Bu sınıfı şöyle çağıralım
Foo a{10};  // new style initialization
Foo b(20);  // old style initialization
Çıktı olarak şunu alırız.
with initializer list
with int
auto Tanımlama
Şöyle yaparız.
auto x={1,2,3};//auto deduces x to std::initializer_list
Şöyle yaparız.
auto items = {1,2,3}; //OK: items is inferred to be std::initializer_list<int>
                      //must #include <initializer_list>
Template Metodlar İle Kullanma
Örnek
Modern Effective C++'ta şöyle bir örnek ver. Elimizde şöyle bir metod olsun.
template<class T>
void f(T t){...}
Daha sonra x değişkeni şöyle kullanılır.
auto x={1,2,3};//auto deduces x to std::initializer_list
f(x);
Örnek
Şöyle yaparız.
template<typename T>
void f(T const & items); 

f({1,2,3}); //ILL-FORMED: T cannot be deduced to be
            //std::initializer_list<int> or anything else
            //even if you use #include <initializer_list>

f(std::initializer_list<int>{1,2,3}); //OK: #include <initializer_list>

f(std::vector<int>{1,2,3});//OK: #include <vector>
                           //because std::vector accepts std::initializer_list<T>
Tek Elemanlı Listeler
Tek elemanlı listeler overload olan metodlarla iyi çalışmıyor. Açıklaması şöyle
  • Otherwise, if the parameter type is not a class and the initializer list has one element, the implicit conversion sequence is the one required to convert the element to the parameter type
Elimizde şöyle bir kod olsun. initializer_list vermemize rağmen int alan metod seçilir.
void f (std::vector<int> v) {...}
void f (int n) {...}


f ({42}); // the int overload is being picked up
Çözümü çift parantez kullanmak. Şöyle yaparız.
f ({{42}});
Eğer std::set alan bir f metodu daha olsaydı şöyle yaparız.
f (std::vector<int>{42});
Return Value
std::initializer_list sonuç dönmek için kullanılamaz. Şöyle yapamayız.
std::initializer_list<int> f() {
  return {1,2,3};
}
Yine aynı şekilde şöyle yapamayız.
auto f() -> std::initializer_list<int>
{
    return {1,2,3};
}
Farklı Tipler
Şu kod derlenmez.
std::initializer_list<int> x{1,2,3,4,5,6};
std::initializer_list<int const> y = x; // error! cannot convert!
Açıklaması şöyle
It sounds like your question is why std::initializer_list<T const> cannot be constructed from std::initializer_list<T> despite the fact that it would be easy to implement such a conversion.

I think the answer is that you are not supposed to have std::initializer_list<T const> in the first place, given that, as you noted, std::initializer_list<T> only gives const access to its elements anyway.

It might therefore be said that there is a "cultural norm" in C++ that you are not supposed to have any constructors that require a std::initializer_list<T const> argument. None of the standard library containers do, for instance, since a cv-qualified value type is illegal anyway (except in the case of std::array, which, of course, has no user-defined constructors anyway).
Initializer List for Döngüsü ile Dolaşılabilir
std::initializer_list ve for Döngüsü yazısına taşıdım.

constructor - default
İmzası şöyle
constexpr initializer_list() noexcept;
constructor - iterator + size
İmzası şöyle.
// The compiler can call a private constructor.
constexpr initializer_list(const_iterator __a, size_type __l);
begin() metodu
İmzası şöyle
// First element.
constexpr const_iterator begin() const noexcept;
Elimizde bir dizi olsun.
int elements[N];
std::initializer_list'i diziye kopyalamak için şöyle yaparız
std::initializer_list<int> data = ...;

size_t size = data.size();
if ( size <= N )
{
  std::copy(data.begin(), data.end(), std::begin(elements));
}
else
{
  std::copy(data.begin(), data.begin()+N, std::begin(elements));
}
end metodu
İmzası şöyle
// One past the last element.
constexpr const_iterator end() const noexcept;
Şöyle yaparız.
std::initializer_list<int> data = ...;

auto it = data.end();
size metodu
İmzası şöyle
// Number of elements.
constexpr size_type size() const noexcept;
Şöyle yaparız
std::initializer_list<int> data = ...;
size_t size = data.size();


Hiç yorum yok:

Yorum Gönder