Türetilmiş sınıf ham dinamik bellek ayırmıyorsa neden temel sınıfın burada sanal bir yıkıcıya ihtiyacı var?


12

Aşağıdaki kod bellek sızıntısına neden oluyor:

#include <iostream>
#include <memory>
#include <vector>

using namespace std;

class base
{
    void virtual initialize_vector() = 0;
};

class derived : public base
{
private:
    vector<int> vec;

public:
    derived()
    {
        initialize_vector();
    }

    void initialize_vector()
    {
        for (int i = 0; i < 1000000; i++)
        {
            vec.push_back(i);
        }
    }
};

int main()
{
    for (int i = 0; i < 100000; i++)
    {
        unique_ptr<base> pt = make_unique<derived>();
    }
}

Sınıf türevi ham dinamik bellek ayırmadığından ve unique_ptr kendiliğinden yerleştiğinden, bu bana pek mantıklı gelmedi. Sınıf temelinin örtülü yıkıcısının türetilen yerine çağrıldığını anlıyorum, ama bunun neden bir sorun olduğunu anlamıyorum. Eğer türetilmiş için açık bir yıkıcı yazsaydım, vec için hiçbir şey yazmazdım.


4
Bir yıkıcının yalnızca elle yazılmış olması halinde var olduğunu varsayıyorsunuz; bu varsayım hatalı: dil, ~derived()vec'in yıkıcısına delege olan bir dil sağlar . Alternatif olarak, unique_ptr<base> ptbunun türetilmiş yıkıcıyı bileceğini varsayıyorsunuz . Sanal bir yöntem olmadan durum böyle olamaz. Bir unique_ptr öğesine herhangi bir çalışma zamanı gösterimi olmayan bir şablon parametresi olan bir silme işlevi verilebilir ve bu özellik bu kod için kullanılmaz.
amon

Kodu kısaltmak için aynı satıra parantez koyabilir miyiz? Şimdi kaydırmam gerekiyor.
laike9m

Yanıtlar:


14

Derleyici delete _ptr;, unique_ptr'yıkıcısının' örtük iç kısmını yürütmeye gittiğinde (burada _ptrişaretçi nerede saklanır unique_ptr), tam olarak iki şeyi bilir:

  1. Silinecek nesnenin adresi.
  2. İşaretçi türü _ptr. İşaretçi olduğu için unique_ptr<base>, bu araçlar _ptrtiptedir base*.

Bütün derleyici bunu biliyor. Bu nedenle, bir tür nesneyi sildiği göz önüne alındığında base, çağrılır ~base().

Öyleyse ... aslında işaret ettiği derviednesneyi yok ettiği kısım nerede ? Derleyici bir tahrip olduğunu bilmiyorsa Çünkü , o zaman biliyorum gelmez hiç var , onun yok edilmesi gerektiğini dursun. Yani nesneyi yarısını yok edilmeden bırakarak kırdınız.derivedderived::vec

Derleyici olamaz varsayalım herhangi olduğunu base*tahrip varlık aslında bir olduğunu derived*; sonuçta, herhangi bir sayıda sınıftan türetilmiş olabilir base. Bu özelin hangi türe base*işaret ettiğini nasıl bilebilir ?

Derleyicinin yapması gereken, aramak için doğru yıkıcıyı bulmaktır (evet, derivedbir yıkıcı vardır. Bir yıkıcı olmadıkça = delete, her sınıf , bir tane yazsanız da yazmasanız da, bir yıkıcıya sahiptir). Bunu yapmak baseiçin, çağıran yıkıcı kodun doğru adresini elde etmek için saklanan bazı bilgileri kullanması gerekecektir , gerçek sınıfın kurucusu tarafından belirlenen bilgiler. Daha sonra bu bilgiyi base*bir işaretçiyi karşılık gelen derivedsınıfın adresine dönüştürmek için kullanmalıdır (farklı bir adreste olabilir veya olmayabilir). Evet, gerçekten). Ve sonra o yıkıcıyı çağırabilir.

Az önce tarif ettiğim mekanizma? Genellikle "sanal dağıtım" olarak adlandırılır: aka, virtualbir temel sınıfa işaretçi / referansınız olduğunda işaretli bir işlevi her çağırdığınızda gerçekleşen şeydir .

Tüm sahip olduğunuz bir temel sınıf işaretçisi / başvurusu olduğunda türetilmiş bir sınıf işlevini çağırmak istiyorsanız, o işlev bildirilmelidir virtual. Yıkıcılar bu bakımdan temelde farklı değildir.


0

miras

Kalıtımın bütün noktası, türetilmiş bir sınıfın bir örneğinin, türetilmiş herhangi bir türden herhangi bir diğer örneğe aynı şekilde muamele edilebileceği şekilde birçok farklı uygulama arasında ortak bir arayüz ve protokol paylaşmaktır.

C ++ 'da miras uygulama detaylarını beraberinde getirir, yıkıcıyı sanal olarak işaretlemek (veya işaretlememek) böyle bir uygulama detayıdır.

İşlev Bağlama

Şimdi bir fonksiyon veya bir kurucu ya da yıkıcı gibi özel durumları çağrıldığında, derleyici hangi fonksiyon uygulamasının kastedildiğini seçmelidir. Daha sonra bu amacı izleyen makine kodu üretmelidir.

Bunu yapmanın en basit yolu, işlevi derleme zamanında seçmek ve herhangi bir değerden bağımsız olarak, bu kod parçası yürütüldüğünde, her zaman işlevin kodunu çalıştırması için yeterli makine kodu yaymak olacaktır. Bu, kalıtım dışında harika çalışıyor.

Bir fonksiyona sahip bir temel sınıfımız varsa (yapıcı veya yıkıcı dahil herhangi bir fonksiyon olabilir) ve kodunuz üzerinde bir fonksiyon çağırıyorsa, bu ne anlama geliyor?

Örneğinizden initialize_vector(), derleyiciyi çağırdıysanız, bulunan uygulamayı Baseveya bulunan uygulamayı gerçekten çağırmak isteyip istemediğinize karar vermesi gerekir Derived. Buna karar vermenin iki yolu vardır:

  1. Birincisi, bir Basetürden aradığınız için uygulamada demek istediğinize karar vermektir Base.
  2. İkincisi, Baseyazılan değerde saklanan değerin çalışma zamanı türünün olabileceği Baseveya Derivedhangi çağrının yapılacağına ilişkin kararın çağrıldığında (her çağrıldığında) çalışma zamanında yapılması gerektiğine karar vermektir .

Bu noktada derleyici karıştırılır, her iki seçenek de eşit derecede geçerlidir. Bu, virtualkarışıma geldiğinde. Bu anahtar kelime bulunduğunda, derleyici, kod gerçek bir değerle çalışana kadar tüm olası uygulamalar arasındaki kararı geciktiren seçenek 2'yi seçer. Bu anahtar kelime yoksa, derleyici seçenek 1'i seçer, çünkü bu normal bir davranıştır.

Derleyici, sanal işlev çağrısı durumunda yine de 1. seçeneği seçebilir. Ama sadece bunun her zaman böyle olduğunu kanıtlayabilirse.

Yapıcılar ve Yıkıcılar

Öyleyse neden sanal bir Oluşturucu belirtmiyoruz?

Daha sezgisel nasıl derleyici için yapıcı özdeş uygulamaları arasında alacağını Derivedve Derived2? Bu oldukça basit, yapamıyor. Derleyicinin gerçekten neyi amaçladığını öğrenebileceği önceden var olan bir değer yoktur. Önceden var olan bir değer yoktur çünkü kurucunun işi budur.

Öyleyse neden sanal bir yıkıcı belirtmemiz gerekiyor?

Daha sezgisel olarak derleyici Baseve için uygulamalar arasında nasıl seçim yapar Derived? Bunlar sadece işlev çağrılarıdır, bu nedenle işlev çağrısı davranışı gerçekleşir. Bildirilen bir sanal yıkıcı olmadan, derleyici Base, değer çalışma zamanı türünden bağımsız olarak doğrudan yıkıcıya bağlanmaya karar verecektir .

Birçok derleyicide, türetilmiş herhangi bir veri üyesi bildirmez veya başka türlerden miras almazsa, içindeki davranış ~Base()uygun olacaktır, ancak garanti edilmez. Tamamen ateşlenmemiş bir alev makinesinin önünde durmak gibi, tamamen tesadüfen çalışır. Bir süre iyisin.

C ++ 'da herhangi bir taban veya arabirim türünü bildirmenin tek doğru yolu, sanal bir yıkıcı bildirmektir, böylece bu türün tür hiyerarşisinin belirli bir örneği için doğru yıkıcı çağrılır. Bu, örneğin en fazla bilgisi olan işlevin, o örneği doğru şekilde temizlemesini sağlar.

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.