Neden C ++ 'da saf bir sanal yıkıcıya ihtiyacımız var?


154

Sanal bir yıkıcıya olan ihtiyacı anlıyorum. Ama neden saf bir sanal yıkıcıya ihtiyacımız var? C ++ makalelerinden birinde yazar, bir sınıf özeti yapmak istediğimizde saf sanal yıkıcı kullandığımızdan bahsetmiştir.

Ancak üye işlevlerinden herhangi birini saf sanal olarak yaparak sınıf özeti yapabiliriz.

Yani sorularım

  1. Bir yıkıcıyı ne zaman gerçekten sanal hale getiriyoruz? Herkes gerçek zamanlı iyi bir örnek verebilir mi?

  2. Soyut sınıflar yaratırken yıkıcıyı da saf sanal yapmak iyi bir uygulama mudur? Evet ise ... o zaman neden?



14
@ Daniel- Bahsedilen bağlantılar sorumu yanıtlamıyor. Saf bir sanal yıkıcının neden bir tanımı olması gerektiğini cevaplar. Sorum şu: Neden saf bir sanal yıkıcıya ihtiyacımız var.
Mark

Sebebini bulmaya çalışıyordum, ama soruyu burada zaten sormuştun.
nsivakr

Yanıtlar:


119
  1. Muhtemelen saf sanal yıkıcılara izin verilmesinin gerçek nedeni, onları yasaklamak, dile başka bir kural eklemek anlamına gelecektir ve bu kurala gerek yoktur, çünkü saf bir sanal yıkıcıya izin veren hiçbir kötü etki gelmez.

  2. Hayır, sade eski sanal yeterlidir.

Sanal yöntemleri için varsayılan uygulamaları olan bir nesne oluşturursanız ve herhangi birini belirli bir yöntemi geçersiz kılmaya zorlamadan soyut yapmak istiyorsanız , yıkıcıyı saf sanal yapabilirsiniz. İçinde fazla bir nokta görmüyorum ama bu mümkün.

Derleyici türetilmiş sınıflar için örtük bir yıkıcı üreteceğinden, sınıfın yazarı bunu yapmazsa, türetilmiş sınıfların soyut olmayacağını unutmayın . Bu nedenle, temel sınıfta saf sanal yıkıcıya sahip olmak türetilmiş sınıflar için herhangi bir fark yaratmayacaktır. Sadece temel sınıf soyut yapacak ( @kappa'nın yorumu için teşekkürler ).

Ayrıca, her türetici sınıfın muhtemelen belirli bir temizleme koduna sahip olması ve salt sanal yıkıcıyı bir hatırlatma olarak kullanması gerektiğini varsayabiliriz, ancak bu, anlaşılmış (ve uygulanmamış) görünüyor.

Not: yıkıcı bile, yalnızca yöntem olup , sanal olarak saf olan bir örneğini (Evet, yalnızca sanal fonksiyonları uygulamaları olabilir) sınıfları türetilen amacıyla bir uygulaması için.

struct foo {
    virtual void bar() = 0;
};

void foo::bar() { /* default implementation */ }

class foof : public foo {
    void bar() { foo::bar(); } // have to explicitly call default implementation.
};

13
"evet saf sanal fonksiyonların uygulamaları olabilir" O zaman saf sanal değildir.
GManNickG

2
Bir sınıf özeti yapmak istiyorsanız, tüm kurucuları korumak daha kolay olmaz mıydı?
bdonlan

79
@GMan, yanılıyorsunuz, saf sanal olarak türetilmiş sınıfların bu yöntemi geçersiz kılması gerekir, bu bir uygulamaya sahip olmak için diktir. Kodumu kontrol et ve foof::barkendin görmek istiyorsan yorum yap.
Motti

15
@GMan: C ++ SSS lite, "Saf bir sanal işlev için bir tanım sağlamanın mümkün olduğunu unutmayın, ancak bu genellikle acemi karıştırır ve daha sonradan en iyi şekilde kaçınılmalıdır." parashift.com/c++-faq-lite/abcs.html#faq-22.4 Wikipedia (bu doğruluk kalesi) de aynı şekilde söylüyor. ISO / IEC standardının benzer bir terminoloji kullandığına inanıyorum (maalesef şu anda kopyanız iş başında) ... Kafa karıştırıcı olduğunu kabul ediyorum ve genellikle bir tanım verirken terimi açıklığa kavuşturmadan kullanmıyorum, özellikle yeni programcılar etrafında ...
leander

9
@Motti: Burada ilginç olan ve daha fazla karışıklık yaratan şey, saf sanal yıkıcının türetilmiş (ve örneklenmiş) sınıfta açıkça geçersiz kılınması gerekmediğidir. Böyle bir durumda örtük tanım kullanılır :)
kappa

33

Soyut bir sınıf için ihtiyacınız olan tek şey en az bir saf sanal işlevdir. Herhangi bir işlev yapacak; ama olduğu gibi, yıkıcı her sınıfın sahip olacağı bir şeydir - bu yüzden daima bir aday olarak oradadır. Dahası, yıkıcıyı saf sanal hale getirmenin (sadece sanal olanın aksine), sınıfı soyut yapmaktan başka davranışsal yan etkileri yoktur. Bu nedenle, birçok stil kılavuzu, bir sınıfın soyut olduğunu belirtmek için saf sanal destuctor'un sürekli olarak kullanılmasını önerir - eğer tutarlı bir yer sağlamazsa, kodu okuyan biri sınıfın soyut olup olmadığını görmek için bakabilir.


1
ama yine de neden saf virtaul yıkıcısının uygulanmasını sağlamak. Ne yanlış gidebilir ne bir yıkıcı saf sanal yapmak ve onun uygulama sağlamaz. Ben sadece temel sınıflar işaretçiler ilan ve bu nedenle soyut sınıf yıkıcı asla çağrılır varsayıyorum.
Krishna Oza

4
@Surfing: çünkü türetilmiş bir sınıfın yıkıcısı, bu yıkıcı saf sanal olsa bile, temel sınıfın yıkıcısını dolaylı olarak çağırır. Yani bunun için bir uygulama yoksa tanımsız bir davranış oluşacaktır.
a.peganz

19

Soyut bir temel sınıf oluşturmak istiyorsanız:

  • O başlatılamaz (evet, bu terimin "soyut" birlikte fazla!)
  • ancak sanal yıkıcı davranışa ihtiyaç duyar (türetilmiş türlere işaretçiler yerine ABC'ye işaretçiler taşımak ve bunlar aracılığıyla silmek niyetindesiniz)
  • ancak başka bir sanal sevk gerekmez diğer yöntemler için davranışını (belki orada vardır başka yöntemler? korumalı bir basit ama çok başka bir kurucular / yıkıcı / atama ihtiyacı "kaynak" kapsayıcı düşünün)

... o yıkıcı saf sanal yaparak sınıf soyut yapmak en kolay ve bunun için bir tanım (yöntem gövdesini) sağlanması.

Varsayımsal ABC'miz için:

Bunun somutlaştırılamayacağını garanti edersiniz (sınıfın kendisinde bile olsa, bu yüzden özel kurucular yeterli olmayabilir), yıkıcı için istediğiniz sanal davranışı elde edersiniz ve "sanal" olarak sanal dağıtım gerekmez.


8

Sorunuza okuduğum cevaplardan, saf bir sanal yıkıcı kullanmak için iyi bir neden bulamadım. Örneğin, aşağıdaki neden beni hiç ikna etmiyor:

Muhtemelen saf sanal yıkıcılara izin verilmesinin gerçek nedeni, onları yasaklamak, dile başka bir kural eklemek anlamına gelecektir ve bu kurala gerek yoktur, çünkü saf bir sanal yıkıcıya izin veren hiçbir kötü etki gelmez.

Bence saf sanal yıkıcılar yararlı olabilir. Örneğin, kodunuzda iki sınıf myClassA ve myClassB olduğunu ve myClassB'nin myClassA'dan miras aldığını varsayalım. Scott Meyers tarafından "Daha Etkili C ++", Madde 33 "Yaprak olmayan sınıfları soyut yapma" kitabında belirtilen nedenlerden dolayı, myClassA ve myClassB'nin miras aldığı soyut bir myAbstractClass sınıfı oluşturmak daha iyi bir uygulamadır. Bu, daha iyi bir soyutlama sağlar ve örneğin nesne kopyaları ile ortaya çıkan bazı sorunları önler.

Soyutlama sürecinde (myAbstractClass sınıfı yaratmanın) hiçbir myClassA veya myClassB yönteminin saf bir sanal yöntem olmak için iyi bir aday olmaması olabilir (myAbstractClass'ın soyut olması için bir önkoşuldur). Bu durumda, soyut sınıfın yıkıcı saf sanal tanımlayın.

Bundan sonra bazı kodlardan somut bir örnek yazdım. Ortak özellikleri paylaşan iki sınıfım var. Bu yüzden onların soyut sınıf IParams'tan miras kalmasına izin verdim. Bu durumda, elimde tamamen sanal olabilecek hiçbir yöntemim yoktu. Örneğin, setParameter yöntemi her alt sınıf için aynı gövdeye sahip olmalıdır. Sahip olduğum tek seçenek IParams'ın yıkıcısını saf sanal yapmaktı.

struct IParams
{
    IParams(const ModelConfiguration& aModelConf);
    virtual ~IParams() = 0;

    void setParameter(const N_Configuration::Parameter& aParam);

    std::map<std::string, std::string> m_Parameters;
};

struct NumericsParams : IParams
{
    NumericsParams(const ModelConfiguration& aNumericsConf);
    virtual ~NumericsParams();

    double dt() const;
    double ti() const;
    double tf() const;
};

struct PhysicsParams : IParams
{
    PhysicsParams(const N_Configuration::ModelConfiguration& aPhysicsConf);
    virtual ~PhysicsParams();

    double g()     const; 
    double rho_i() const; 
    double rho_w() const; 
};

1
Ben bu kullanımı seviyorum, ama miras "zorlamak" için başka bir yol IParamdiğer bazı yorumda belirtildiği gibi, korunacak yapıcı ilan etmektir .
rwols

4

Zaten uygulanmış ve test edilmiş türev sınıfınızda herhangi bir değişiklik yapmadan temel sınıfın örneklenmesini durdurmak istiyorsanız, temel sınıfınıza saf bir sanal yıkıcı uygularsınız.


3

Burada ne zaman sanal yıkıcıya ihtiyacımız olduğunu ve ne zaman saf sanal yıkıcıya ihtiyacımız olduğunu söylemek istiyorum

class Base
{
public:
    Base();
    virtual ~Base() = 0; // Pure virtual, now no one can create the Base Object directly 
};

Base::Base() { cout << "Base Constructor" << endl; }
Base::~Base() { cout << "Base Destructor" << endl; }


class Derived : public Base
{
public:
    Derived();
    ~Derived();
};

Derived::Derived() { cout << "Derived Constructor" << endl; }
Derived::~Derived() {   cout << "Derived Destructor" << endl; }


int _tmain(int argc, _TCHAR* argv[])
{
    Base* pBase = new Derived();
    delete pBase;

    Base* pBase2 = new Base(); // Error 1   error C2259: 'Base' : cannot instantiate abstract class
}
  1. Hiç kimsenin doğrudan Base sınıfı nesnesini oluşturamamasını istediğinizde, saf sanal yıkıcı kullanın virtual ~Base() = 0. Genellikle en az bir saf sanal işlev gereklidir, virtual ~Base() = 0bu işlev olarak alalım .

  2. Yukarıdaki şeylere ihtiyacınız olmadığında, sadece Türetilmiş sınıf nesnesinin güvenli bir şekilde yok edilmesine ihtiyacınız vardır.

    Baz * pBase = yeni Derived (); pBase'i sil; saf sanal yıkıcı gerekli değildir, sadece sanal yıkıcı işi yapacaktır.


2

Bu cevaplarla varsayımlara giriyorsunuz, bu yüzden netlik uğruna daha basit, daha aşağıya doğru bir açıklama yapmaya çalışacağım.

Nesneye yönelik tasarımın temel ilişkileri ikidir: IS-A ve HAS-A. Ben uydurmadım. Buna böyle denir.

IS-A, belirli bir nesnenin, sınıf hiyerarşisinde üstünde olan sınıfın varlığını tanımladığını belirtir. Muz nesnesi, meyve sınıfının bir alt sınıfı ise bir meyve nesnesidir. Bu, bir meyve sınıfının kullanılabileceği her yerde, bir muzun kullanılabileceği anlamına gelir. Yine de refleksif değildir. Belirli bir sınıf çağrılırsa, belirli bir sınıfın temel sınıfını değiştiremezsiniz.

Has-a, bir nesnenin bileşik sınıfın bir parçası olduğunu ve bir mülkiyet ilişkisi olduğunu belirtir. C ++ 'da üye bir nesne olduğu anlamına gelir ve bu nedenle onus, kendini yok etmeden önce onu atmak veya sahipliğini elden çıkarmak için kendi sınıfındadır.

Bu iki kavramın tek miras dillerinde gerçekleştirilmesi, c ++ gibi bir çoklu miras modelinden daha kolaydır, ancak kurallar esasen aynıdır. Karmaşıklık, bir Banana sınıfı işaretçisini Fruit sınıfı işaretçisini alan bir işleve geçirmek gibi, sınıf kimliği belirsiz olduğunda ortaya çıkar.

Sanal işlevler, öncelikle, bir çalışma zamanı şeyidir. Çalışan programda çağrıldığı sırada hangi işlevin çalıştırılacağına karar verilmesi için polimorfizmin bir parçasıdır.

Sanal anahtar sözcük, sınıf kimliği hakkında belirsizlik varsa işlevleri belirli bir sırada bağlamak için bir derleyici yönergesidir. Sanal işlevler her zaman üst sınıflardadır (bildiğim kadarıyla) ve derleyiciye üye işlevlerinin adlarına bağlanmasının önce alt sınıf işlevi ve daha sonra üst sınıf işlevi ile gerçekleşmesi gerektiğini belirtir.

Fruit sınıfı, varsayılan olarak "NONE" döndüren bir sanal işlev rengine () sahip olabilir. Banana sınıfı color () işlevi "SARI" veya "KAHVERENGİ" döndürür.

Ama bir Meyve işaretçisini alan işlev, kendisine gönderilen Banana sınıfında color () öğesini çağırırsa - hangi color () işlevi çağrılır? İşlev normalde bir Fruit nesnesi için Fruit :: color () yöntemini çağırır.

Bu zamanın% 99'u amaçlanan şey değildir. Ancak Fruit :: color () sanal olarak bildirildiyse, nesne için Banana: color () çağrılır çünkü çağrı sırasında doğru color () işlevi Fruit pointer'a bağlanır. Çalışma zamanı, Fruit sınıfı tanımında sanal olarak işaretlendiğinden işaretçinin hangi nesneyi gösterdiğini kontrol eder.

Bu, bir alt sınıftaki bir işlevi geçersiz kılmaktan farklıdır. Bu durumda, meyve işaretçisi Fruit :: color () 'i çağırır.

Şimdi bir "saf sanal işlev" fikri ortaya çıkıyor. Saflığın onunla hiçbir ilgisi olmadığı için oldukça talihsiz bir ifade. Temel sınıf yönteminin asla çağrılmaması gerektiği anlamına gelir. Gerçekten saf bir sanal işlev çağrılamaz. Ancak yine de tanımlanması gerekir. Bir işlev imzası bulunmalıdır. Birçok kodlayıcı, eksiksizlik için boş bir uygulama {} yapar, ancak derleyici değilse dahili olarak bir tane oluşturur. Bu durumda, işaretçi Fruit konumunda olsa bile işlev çağrıldığında Banana :: color (), color () öğesinin tek uygulaması olduğu için çağrılır.

Şimdi bulmacanın son parçası: inşaatçılar ve yıkıcılar.

Saf sanal kurucular tamamen yasadışıdır. Bu sadece çıktı.

Ancak saf sanal yıkıcılar, temel sınıf örneğinin oluşturulmasını yasaklamak istediğinizde işe yarar. Temel sınıfın yıkıcısı saf sanal ise yalnızca alt sınıflar örneklenebilir. kural 0'a atamaktır.

 virtual ~Fruit() = 0;  // pure virtual 
 Fruit::~Fruit(){}      // destructor implementation

Bu durumda bir uygulama oluşturmanız gerekir. Derleyici bunun ne yaptığınızı bilir ve doğru yaptığınızdan emin olur ya da derlemek için ihtiyaç duyduğu tüm işlevlere bağlanamayacağından kasten şikayet eder. Sınıf hiyerarşinizi nasıl modellediğiniz konusunda doğru yolda değilseniz hatalar kafa karıştırıcı olabilir.

Yani bu durumda Meyve örnekleri oluşturmak yasaktır, ancak Muz örnekleri oluşturmanıza izin verilir.

Bir Banana örneğine işaret eden Meyve işaretçisini silme çağrısı, önce Banana :: ~ Banana () öğesini ve ardından her zaman Fuit :: ~ Fruit () öğesini çağırır. Çünkü ne olursa olsun, bir alt sınıf yıkıcı dediğinizde, temel sınıf yıkıcı bunu izlemelidir.

Kötü bir model mi? Tasarım aşamasında daha karmaşıktır, evet, ancak çalışma zamanında doğru bağlantının yapılmasını ve tam olarak hangi alt sınıfa erişildiği konusunda belirsizliğin olduğu bir alt sınıf işlevinin gerçekleştirilmesini sağlayabilir.

Yalnızca genel veya belirsiz işaretçiler olmadan tam sınıf işaretçileri geçecek şekilde C ++ yazarsanız, sanal işlevlere gerçekten gerek yoktur. Ancak, türlerin çalışma zamanı esnekliğine ihtiyacınız varsa (Apple Banana Orange ==> Meyve'de olduğu gibi) işlevler, daha az yedekli kodla daha kolay ve çok yönlü hale gelir. Artık her meyve türü için bir işlev yazmak zorunda değilsiniz ve her meyvenin renge () kendi doğru işlevi ile yanıt vereceğini biliyorsunuz.

Umarım bu uzun soluklu açıklama, şeyleri karıştırmaktan ziyade kavramı sağlamlaştırır. Orada bakmak ve yeterince bakmak ve aslında onları çalıştırmak ve onlarla karışıklık bakmak için çok iyi örnekler vardır ve bunu elde edersiniz.


1

Bu on yıllık bir konudur :) Ayrıntılar için "Etkili C ++" kitabındaki 7. Maddenin son 5 paragrafını okuyun, "Bazen bir sınıfa sanal sanal yıkıcı vermek uygun olabilir ..."


0

Bir örnek istediniz ve inanıyorum ki aşağıdakiler saf bir sanal yıkıcı için bir neden sunuyor. Bunun iyi bir neden olup olmadığına dair cevapları dört gözle bekliyorum ...

Kimsenin error_basetipini atmasını istemiyorum , ancak istisna türleri error_oh_shucksve error_oh_blastaynı işlevselliğe sahip ve bunu iki kez yazmak istemiyorum. PImpl karmaşıklığı std::stringmüşterilerime maruz kalmaktan kaçınmak için gereklidir ve kullanımı std::auto_ptrkopya yapıcısını gerektirir.

Genel üstbilgi, kitaplığım tarafından atılan farklı özel durum türlerini ayırt etmek için istemci tarafından kullanılabilecek özel durum özelliklerini içerir:

// error.h

#include <exception>
#include <memory>

class exception_string;

class error_base : public std::exception {
 public:
  error_base(const char* error_message);
  error_base(const error_base& other);
  virtual ~error_base() = 0; // Not directly usable

  virtual const char* what() const;
 private:
  std::auto_ptr<exception_string> error_message_;
};

template<class error_type>
class error : public error_base {
 public:
   error(const char* error_message) : error_base(error_message) {}
   error(const error& other) : error_base(other) {}
   ~error() {}
};

// Neither should these classes be usable
class error_oh_shucks { virtual ~error_oh_shucks() = 0; }
class error_oh_blast { virtual ~error_oh_blast() = 0; }

Ve işte paylaşılan uygulama:

// error.cpp

#include "error.h"
#include "exception_string.h"

error_base::error_base(const char* error_message)
  : error_message_(new exception_string(error_message)) {}

error_base::error_base(const error_base& other)
  : error_message_(new exception_string(other.error_message_->get())) {}

error_base::~error_base() {}

const char* error_base::what() const {
  return error_message_->get();
}

Özel tutulan exception_string sınıfı, genel arayüzümden std :: string'i gizler:

// exception_string.h

#include <string>

class exception_string {
 public:
  exception_string(const char* message) : message_(message) {}

  const char* get() const { return message_.c_str(); }
 private:
  std::string message_;
};

Kodum daha sonra şu şekilde bir hata atar:

#include "error.h"

throw error<error_oh_shucks>("That didn't work");

Bir şablonun kullanımı errorbiraz zahmetlidir. İstemcilerin hataları yakalamasını gerektirme pahasına bir miktar kod kaydeder:

// client.cpp

#include <error.h>

try {
} catch (const error<error_oh_shucks>&) {
} catch (const error<error_oh_blast>&) {
}

0

Belki de başka cevaplarda göremediğim saf sanal yıkıcının başka bir GERÇEK KULLANIM DURUMU var :)

İlk başta, işaretli cevaba tamamen katılıyorum: Bunun nedeni saf sanal yıkıcıyı yasaklamanın dil spesifikasyonunda ekstra bir kurala ihtiyaç duymasıdır. Ama hala Mark'ın talep ettiği kullanım durumu değil :)

İlk olarak şunu hayal edin:

class Printable {
  virtual void print() const = 0;
  // virtual destructor should be here, but not to confuse with another problem
};

ve şöyle bir şey:

class Printer {
  void queDocument(unique_ptr<Printable> doc);
  void printAll();
};

Basitçe - arayüz Printableve bu arayüz ile bir şey tutan bazı "konteyner" var. Burada print()yöntemin neden saf sanal olduğu oldukça açık . Bir gövdeye sahip olabilir, ancak varsayılan bir uygulama yoksa, saf sanal ideal bir "uygulama" dır (= "soyundan gelen bir sınıf tarafından sağlanmalıdır").

Ve şimdi aynı şeyi düşünün, baskı için değil, yıkım için:

class Destroyable {
  virtual ~Destroyable() = 0;
};

Ve benzer bir kap da olabilir:

class PostponedDestructor {
  // Queues an object to be destroyed later.
  void queObjectForDestruction(unique_ptr<Destroyable> obj);
  // Destroys all already queued objects.
  void destroyAll();
};

Gerçek uygulamamdan basitleştirilmiş kullanım örneği. Buradaki tek fark, "normal" yerine "özel" yöntemin (yıkıcı) kullanılmasıdır print(). Ancak saf sanal olmasının nedeni hala aynı - yöntem için varsayılan bir kod yok. Biraz kafa karıştırıcı olabilir, etkili bir şekilde bazı yıkıcı OLMALIDIR ve derleyici aslında bunun için boş bir kod üretir. Ama bir programcı açısından bakıldığında saf sanallık hala şu anlama gelir: "Herhangi bir varsayılan kodum yok, türetilmiş sınıflar tarafından sağlanmalı."

Bence burada büyük bir fikir yok, sadece saf sanallığın gerçekten tekdüze bir şekilde çalıştığı hakkında daha fazla açıklama - yıkıcılar için de.


-2

1) Türetilmiş sınıfların temizlik yapmasını istediğinizde. Bu nadir.

2) Hayır, ama bunun sanal olmasını istiyorsunuz.


-2

yıkıcıyı sanal yapmak zorundayız, eğer yıkıcıyı sanal yapmazsak derleyici sadece temel sınıfın içeriğini tahrip eder, tüm türetilmiş sınıflar değişmeden kalır, çünkü bacalı derleyici başkalarının yıkıcısını çağırmaz temel sınıf hariç.


-1: Soru, bir yıkıcının neden sanal olması gerektiği ile ilgili değil.
Troubadour

Dahası, bazı durumlarda yıkıcıların doğru yıkımı sağlamak için sanal olmaları gerekmez. Sanal yıkıcılar, yalnızca deletetürevini işaret ettiğinde temel sınıf için bir işaretçi çağırmanız gerektiğinde gereklidir .
CygnusX1

% 100 haklısın. Bu, geçmişte C ++ programlarındaki bir numaralı sızıntı ve çökme kaynaklarından biridir ve üçüncü olarak yalnızca boş işaretçilerle bir şeyler yapmaya çalışmak ve dizilerin sınırlarını aşmaktır. Sanal olmayan bir temel sınıf yıkıcı, genel işaretçi üzerinde, sanal olarak işaretlenmemişse alt sınıf yıkıcısını tamamen atlayarak çağrılır. Alt sınıfa ait dinamik olarak oluşturulmuş nesneler varsa, silme çağrısında temel yıkıcı tarafından kurtarılmazlar. Sen BLUURRK sonra iyi birlikte çekiyorsun! (nerede bulmak da zor.)
Chris Reid
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.