30 Kasım 2020 Pazartesi

Named Return Value Optimization (NRVO) - Constructor ve Destructor Metodları Çağrılmaz.

Giriş
Return Value Optimization (RVO) iki çeşittir.
1. Unnamed RVO
2. Named RVO

Named RVO Nedir?
Named RVO Copy Elision kuralının özel bir durumu. Bu kural sayesinde l-value bir nesne r-value bir nesneye dönüşür. 

CopyElision ve onun alt kümesi olan NRVO C++11 ve C++14 ile mecburi değil ancak C++17 ile mecburi hale geliyor.

NRVO'nun Etkisi Nedir?
NRVO uygulanırsa return edilen nesnenin copy/move constructor metodu çağrılmaz. Constructor çağrılmadığı için destructor'ı varsa o da çağrılmaz. Bu durum şaşırtıcı sonuçlara sebep olabiliyor. Açıklaması şöyle
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects.
Örnek
NRVO'nun olduğunu görmek için şöyle bir kod olsun.
std::vector<int> foo() {
 int i;
 std::vector<int> a(100);
 printf("%p, %p, %p\n", &i, &a, &(a[0]));
 return a;
}

int main() {
 int i;
 std::vector<int> b = foo();
 printf("%p, %p, %p\n", &i, &b, &(b[0]));
}
Çalıştırmak için şöyle yaparız. Hem foo() metodundaki hem de main() metodundaki std::vector nesnesinin adresi aynıdır.
$ vim main.cpp 
$ cc -std=c++11 -lc++ main.cpp
$ ./a.out
0x7ffee28d28ac, 0x7ffee28d28f0, 0x7ff401402c00
0x7ffee28d290c, 0x7ffee28d28f0, 0x7ff401402c00
$ 
std::string
Copy constructor veya move constructor, destructor metodları copy elision sayesinde çağrılmaz.
std::string system_call(const char *cmd){
  std::string a;
  ...
  return a;
}

std::string st = system_call("whatever code"); //system_call metoduna direkt st geçilir
std::unique_ptr
Copy constructor metodu olmayan bir nesne olan unique_ptr kullanan bu kod, copy elision sayesinde derlenir.
#include <iostream>
#include <memory>

using namespace std;

unique_ptr<int> foo()
{
  unique_ptr<int> p( new int(10) );

  return p;                   // 1
  //return move( p );         // 2
}

int main()
{
  unique_ptr<int> p = foo();

  cout << *p << endl;
  return 0;
}
std::vector
Örnek
Şöyle yaparız.
std::vector<huge_thing> foo()
{
  std::vector<huge_thing> result{/* ... */};
  return result;
}

void bar()
{
  auto v = foo(); // (0)
}
Kendi Sınıfım
Örnek
Şöyle yaparız. Copy constructor delete olduğu halde derlenir
struct Foo {
    Foo() = default;
    Foo(const Foo&) = delete;
};

int main() {
  // Works in C++17 and C++20, fails in C++14 and before
  Foo foo = Foo(); 
}
Örnek
Şöyle yaparız. Copy constructor ve destructor çalışmaz
struct Foo {
  Foo() { std::cout << "Constructed" << std::endl; }

  Foo(const Foo &) { std::cout << "Copy-constructed" << std::endl; }

  Foo(Foo &&) { std::cout << "Move-constructed" << std::endl; }

  ~Foo() { std::cout << "Destructed" << std::endl; }
};

Foo foo() {
  Foo mystr;
  return mystr;
}

int main() {
  Foo result = foo();
}
Örnek
Şöyle yaparız. Burada NRVO uygulanırsa Foo sınıfının destructor metodu çalışmaz. Bu durumda main metodunun çıktısı 0 olur
// Foo adds an element to a std::vector passed by reference
// on construction in the destructor
struct Foo {
  Foo(std::vector<double>& v) : m_v(v){
  }
  ~Foo(){
    m_v.push_back(1.0);
  }
  std::vector<double>& m_v;
};

std::vector<double> bar(){
  std::vector<double> ret;
  Foo foo(ret);
  return ret;
}

int main(){
  std::cout << bar().size() << "\n";
}

Hiç yorum yok:

Yorum Gönder