25 Kasım 2019 Pazartesi

explicit Constructor

Giriş
Constructor Tipleri yazısında C++ ile kullanılabilecek diğer constructor'lar var

1. explicit constructor - C++11'den önce
Derleyicinin bir nesneyi yaratmak için kendi kendine verilen parametrenin tipini değiştirmesi engellemek demek.
Örnek
Foo sınıfı şöyle tanımlanmış olsun
class Foo
{
public:
  // single parameter constructor, can be used as an implicit conversion
  Foo (int foo)
  {...}
  ...

};
DoBar isimli ve Foo alan bir metodumuz olsun.
void DoBar (Foo foo)
{
  ...  
}
DoBar şöyle çağrılabilir.
int main ()
{
  DoBar (42);
}
Ayrıca Foo şöyle yaratılabilir.
int main() {
    Foo f = 2;
}
Eğer constructor içinde explicit kelimesini kullanırsak Foo şöyle yaratılmak zorunda kalınır.
Foo f (2);
2. explicit constructor - C++11'den sonra
Tek parametreli constructor'lar için explicit yazmak yetersiz. Brace Initialization yüzünden çok parametreli metodlara da explicit yazmak gerekir.
Örnek
Şöyle yaparız.
struct Bar { explicit Bar(int, int); };

Bar b1(1, 1); // ok
Bar b2 {1, 1}; // ok
Bar b3 = {1, 1}; // NOT OKAY
Örnek
Böylece dizi olarak şöyle yapamayız.
struct A {
  explicit A( int b, int c ) {}
};

struct B {
  B( int b, int c ) {}
};

int main() {
    B b[] = {{1,2}, {3,5}}; // OK

    A a1[] = {A{1,2}, A{3,4}}; // OK

    A a2[] = {{1,2}, {3,4}}; // Error

    return 0;
}
Örnek
Böylece metod çağrılarında şunu yapamayız.
void bar(const Bar&) {}

bar({ 42, 42}); // invalid
3. explicit kullanmazsak
Bence explicit tek parametreli constructor'larda her zaman kullanılmalı.
Örnek
Elimizde şöyle bir sınıf olsun.
#include<iostream>

using namespace std;

class MyClass
{
  string figName;
public:
  MyClass(const string& s)
  {
    figName = s;
  }

  const string& getName() const
  {
    return figName;
  }
};

ostream& operator<<(ostream& ps, const MyClass& f)
{
  os << f.getName();
  return os;
}

int main()
{
  MyClass f1("Hello");
  cout << f1;
}
Bu kod stackoverflow exception fırlatır. Açıklaması şöyle
With MS VC++ compiler error happens because if you do not #include <string> you won't have operator<< defined for std::string.

When compiler tries to compile os << f.getName(); it looks for operator<< defined for std::string. Since it was not defined, compiler looks for alternatives. There is operator<< defined for MyClass and compiler tries to use it, and to use it it has to convert std::string to MyClass and this is exactly what happens because MyClass has non-explicit constructor! So, compiler ends up creating new instance of your MyClass and tries to stream it again to your output stream. This results in endless recursion:

4. Explicit Kullanırken Bazı Tipleri Kabul Etmemek
Örnek
Elimizde şöyle bir kod olsun.
class A
{
public:
  explicit A(int a)
  {
    num = a;
  }

  int num;
};
Normalde int kullanmak gerekirken double ile de nesneyi ilklendirebiliyoruz. Şöyle yaparız
int main()
{
    A a1 = A(10.0);
    std::cout << a1.num;
    return 0;
}
Sadece int tipine izin vermek için şöyle yaparız.
struct A
{
  explicit A(int a)
    : num(a)
  {}

  template<class T>
  A(T) = delete;

  int num;
};

int main()
{
  //A a1=A(10.0); // error: use of deleted function 'A::A(T) [with T = double]'
  A a2 = A(10); // OK
}
Explicit Constructor Kullanılmadığında Uyarı Vermek
Örnek
Sınıfın implicit conversion yapan constructor kodu olsun. Yani şöyle olsun
class Duration
{
    int seconds;
public:
    Duration(int t_seconds) : seconds(t_seconds) { }
};

int main()
{
    Duration t(30);
    t = 60;
}
Bir zaman sonra explicit constructor yazalım. Eski kod derlensin ancak uyarı versin istersek şöyle yaparız
#include<concepts>

class Duration {
  int seconds;
public:
  template<std::same_as<int> T>
  [[deprecated("uses implicit conversion")]]
  Duration(T t_seconds) : Duration(t_seconds) { }
  
  explicit Duration(int t_seconds) : seconds(t_seconds) { }
};
Açıklaması şöyle
If you want to allow t = 0.6, just change the std:same_as to std::convertible_to



2 yorum: