Виртуальный метод, но не виртуальный деструктор [дубликат]

В C ^ есть побитовая XOR:

0101 ^ 1100 = 1001 // in binary

Для питания нет оператора, вам нужно будет использовать функцию pow из math.h (или какой-либо другой подобной функции):

result = pow( a, i );

1217
задан j0k 1 July 2013 в 15:11
поделиться

15 ответов

Виртуальные деструкторы полезны, когда вы можете удалить экземпляр производного класса с помощью указателя на базовый класс:

class Base 
{
    // some virtual methods
};

class Derived : public Base
{
    ~Derived()
    {
        // Do some important cleanup
    }
};

Здесь вы заметите, что я не объявлял деструктор базы virtual. Теперь давайте посмотрим на следующий фрагмент:

Base *b = new Derived();
// use b
delete b; // Here's the problem!

Поскольку деструктор базы не является virtual, а b является Base*, указывающим на объект Derived, delete b имеет неопределенное поведение :

delete b], если статический тип подлежащего удалению объекта отличается от его динамического типа, статический тип должен быть базовый класс динамического типа объекта, подлежащего удалению, и статический тип должен иметь виртуальный деструктор или поведение не определено.

В большинстве реализаций вызов деструктора будет разрешен как и любой не виртуальный код, что означает, что деструктор базового класса будет вызываться, но не тот, который был получен из производного класса, что приведет к утечке ресурсов.

Подводя итог, всегда создавайте деструкторы базовых классов virtual, когда они предназначены для управления полиморфно.

Если вы хотите предотвратить удаление экземпляра с помощью указателя базового класса, вы можете сделать деструктор базового класса защищенным и не виртуальным; при этом компилятор не позволит вам называть delete указателем базового класса.

Вы можете узнать больше о деструкторе виртуальности и виртуального базового класса в этой статье от Herb Sutter .

1322
ответ дан Xhynk 27 August 2018 в 01:11
поделиться

Вызов деструктора с помощью указателя на базовый класс

struct Base {
  virtual void f() {}
  virtual ~Base() {}
};

struct Derived : Base {
  void f() override {}
  ~Derived() override {}
};

Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived

Виртуальный вызов деструктора ничем не отличается от любого другого вызова виртуальной функции.

Для base->f() вызов будет отправляться на Derived::f(), а для base->~Base() - то же самое - его функция переопределения - Derived::~Derived().

То же самое происходит, когда деструктор называется косвенно, например delete base;. Оператор delete вызывает base->~Base(), который будет отправлен в Derived::~Derived().

Абстрактный класс с не виртуальным деструктором

Если вы не собираетесь удалять объект через указатель на его базовый класс - тогда нет необходимости иметь виртуальный деструктор. Просто сделайте это protected, чтобы он не был вызван случайно:

// library.hpp

struct Base {
  virtual void f() = 0;

protected:
  ~Base() = default;
};

void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.

//-------------------
// application.cpp

struct Derived : Base {
  void f() override { ... }
};

int main() {
  Derived derived;
  CallsF(derived);
  // No need for virtual destructor here as well.
}
10
ответ дан Abyx 27 August 2018 в 01:11
поделиться

Также имейте в виду, что удаление указателя базового класса, когда нет виртуального деструктора, приведет к неопределенному поведению . Что-то, что я узнал совсем недавно:

Как следует переопределять удаление в C ++?

Я использую C ++ в течение многих лет, и мне все еще удается повесить я.

38
ответ дан Community 27 August 2018 в 01:11
поделиться

Сделать виртуальный дескриптор виртуальным, когда ваш класс является полиморфным.

30
ответ дан Dana 27 August 2018 в 01:11
поделиться

Виртуальный конструктор невозможен, но возможен виртуальный деструктор. Проведем эксперимент ....

#include <iostream>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

Вышеприведенный код выводит следующее:

Base Constructor Called
Derived constructor called
Base Destructor called

Построение производного объекта следует за правилом построения, но когда мы удаляем «b "указатель (базовый указатель) мы обнаружили, что только деструктор базы является вызовом. Но этого не должно быть. Чтобы сделать правильную вещь, мы должны сделать базовый деструктор виртуальным. Теперь посмотрим, что произойдет в следующем:

#include <iostream>

using namespace std;

class Base
{ 
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    virtual ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

Выход изменился следующим образом:

Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called

Итак, уничтожение базового указателя (который принимает выделение на производном объекте! ) следуют за правилом уничтожения, т. е. сначала выведенным потом базой. С другой стороны, для конструктора нет ничего похожего на виртуальный конструктор.

156
ответ дан Daniel Carvalho 27 August 2018 в 01:11
поделиться

Мне нравится думать о интерфейсах и реализациях интерфейсов. В языке C ++ говорят, что это чистый виртуальный класс. Деструктор является частью интерфейса и ожидается, что он будет реализован. Поэтому деструктор должен быть чистым виртуальным. Как насчет конструктора? Конструктор фактически не является частью интерфейса, потому что объект всегда создается явно.

7
ответ дан Dragan Ostojic 27 August 2018 в 01:11
поделиться

Виртуальное ключевое слово для деструктора необходимо, когда вы хотите, чтобы разные деструкторы должны следовать правильному порядку, в то время как объекты удаляются с помощью указателя базового класса. например:

Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ; 

Если деструктор производного класса является виртуальным, объекты будут уничтожены в порядке (сначала производный объект, затем базовый). Если деструктор производного класса НЕ является виртуальным, то только объект базового класса будет удален (поскольку указатель имеет базовый класс «Base * myObj»). Таким образом, будет происходить утечка памяти для производного объекта.

5
ответ дан Mukul Kashmira 27 August 2018 в 01:11
поделиться

Я подумал, что было бы полезно обсудить «неопределенное» поведение или, по крайней мере, неопределенное поведение «сбой», которое может возникнуть при удалении через базовый класс (/ struct) без виртуального деструктора или, точнее, vtable. В приведенном ниже коде перечислены несколько простых структур (то же самое было бы верно для классов).

#include <iostream>
using namespace std;

struct a
{
    ~a() {}

    unsigned long long i;
};

struct b : a
{
    ~b() {}

    unsigned long long j;
};

struct c : b
{
    ~c() {}

    virtual void m3() {}

    unsigned long long k;
};

struct d : c
{
    ~d() {}

    virtual void m4() {}

    unsigned long long l;
};

int main()
{
    cout << "sizeof(a): " << sizeof(a) << endl;
    cout << "sizeof(b): " << sizeof(b) << endl;
    cout << "sizeof(c): " << sizeof(c) << endl;
    cout << "sizeof(d): " << sizeof(d) << endl;

    // No issue.

    a* a1 = new a();
    cout << "a1: " << a1 << endl;
    delete a1;

    // No issue.

    b* b1 = new b();
    cout << "b1: " << b1 << endl;
    cout << "(a*) b1: " << (a*) b1 << endl;
    delete b1;

    // No issue.

    c* c1 = new c();
    cout << "c1: " << c1 << endl;
    cout << "(b*) c1: " << (b*) c1 << endl;
    cout << "(a*) c1: " << (a*) c1 << endl;
    delete c1;

    // No issue.

    d* d1 = new d();
    cout << "d1: " << d1 << endl;
    cout << "(c*) d1: " << (c*) d1 << endl;
    cout << "(b*) d1: " << (b*) d1 << endl;
    cout << "(a*) d1: " << (a*) d1 << endl;
    delete d1;

    // Doesn't crash, but may not produce the results you want.

    c1 = (c*) new d();
    delete c1;

    // Crashes due to passing an invalid address to the method which
    // frees the memory.

    d1 = new d();
    b1 = (b*) d1;
    cout << "d1: " << d1 << endl;
    cout << "b1: " << b1 << endl;
    delete b1;  

/*

    // This is similar to what's happening above in the "crash" case.

    char* buf = new char[32];
    cout << "buf: " << (void*) buf << endl;
    buf += 8;
    cout << "buf after adding 8: " << (void*) buf << endl;
    delete buf;
*/
}

Я не предлагаю, нужны ли вам виртуальные деструкторы или нет, хотя я думаю, что в целом это хорошая практика иметь их. Я просто указываю причину, по которой вы можете столкнуться с сбоем, если ваш базовый класс (/ struct) не имеет vtable и ваш производный класс (/ struct) делает, и вы удаляете объект через базовый класс (/ struct) указатель. В этом случае адрес, который вы передаете в бесплатную процедуру кучи, является недопустимым и, следовательно, причиной сбоя.

Если вы запустите указанный выше код, вы увидите, когда проблема возникнет. Когда этот указатель базового класса (/ struct) отличается от этого указателя производного класса (/ struct), вы столкнетесь с этой проблемой. В приведенном выше примере структуры a и b не имеют vtables. У структур c и d есть vtables. Таким образом, a или b указатель на экземпляр объекта c или d будут исправлены для учета vtable. Если вы передадите этот указатель a или b для его удаления, произойдет сбой из-за того, что адрес недействителен для бесплатной процедуры кучи.

Если вы планируете удалять производные экземпляры, имеющие vtables из указателей базового класса, вам нужно убедитесь, что базовый класс имеет таблицу vtable. Один из способов сделать это - добавить виртуальный деструктор, который вы, возможно, захотите правильно очистить ресурсы.

1
ответ дан nickdu 27 August 2018 в 01:11
поделиться

Что такое виртуальный деструктор или как использовать виртуальный деструктор

Деструктор класса - это функция с тем же именем класса, предшествующего ~, который перераспределит память, выделенную классом. Почему нам нужен виртуальный деструктор

См. Следующий пример с некоторыми виртуальными функциями

В примере также рассказывается, как вы можете преобразовать букву в верхнюю или нижнюю

#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
  //void convertch(){};
  virtual char* convertChar() = 0;
  ~convertch(){};
};

class MakeLower :public convertch
{
public:
  MakeLower(char *passLetter)
  {
    tolower = true;
    Letter = new char[30];
    strcpy(Letter, passLetter);
  }

  virtual ~MakeLower()
  {
    cout<< "called ~MakeLower()"<<"\n";
    delete[] Letter;
  }

  char* convertChar()
  {
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] + 32;
    return Letter;
  }

private:
  char *Letter;
  bool tolower;
};

class MakeUpper : public convertch
{
public:
  MakeUpper(char *passLetter)
  {
    Letter = new char[30];
    toupper = true;
    strcpy(Letter, passLetter);
  }

  char* convertChar()
  {   
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] - 32;
    return Letter;
  }

  virtual ~MakeUpper()
  {
    cout<< "called ~MakeUpper()"<<"\n";
    delete Letter;
  }

private:
  char *Letter;
  bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{
  convertch *makeupper = new MakeUpper("hai"); 
  cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";     
  delete makeupper;
  convertch *makelower = new MakeLower("HAI");;
  cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" "; 
  delete makelower;
  return 0;
}

Из приведенного выше примера вы можете видеть, что деструктор для класса MakeUpper и MakeLower не вызывается.

См. следующий пример с виртуальным деструктором

#include "stdafx.h"
#include<iostream>

using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor

};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
      delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;

}

return Letter;
}

private:
char *Letter;
bool tolower;

};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{

size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
      cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{

convertch *makeupper = new MakeUpper("hai");

cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";

delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";


delete makelower;
return 0;
}

Виртуальный деструктор будет вызывать явно самый производный деструктор времени выполнения класса, чтобы он мог корректно очищать объект.

Или посетите ссылку

https: / /web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138

1
ответ дан peak 27 August 2018 в 01:11
поделиться

Я думаю, что ядро ​​этого вопроса касается виртуальных методов и полиморфизма, а не деструктора. Вот более ясный пример:

class A
{
public:
    A() {}
    virtual void foo()
    {
        cout << "This is A." << endl;
    }
};

class B : public A
{
public:
    B() {}
    void foo()
    {
        cout << "This is B." << endl;
    }
};

int main(int argc, char* argv[])
{
    A *a = new B();
    a->foo();
    if(a != NULL)
    delete a;
    return 0;
}

Будет распечатан:

This is B.

Без virtual он распечатает:

This is A.

И теперь вы должны понимать, когда использовать виртуальные деструкторы.

0
ответ дан Peter Hall 27 August 2018 в 01:11
поделиться

Чтобы быть простым, виртуальный деструктор должен уничтожить ресурсы в правильном порядке, когда вы удаляете указатель базового класса, указывающий на объект производного класса.

 #include<iostream>
 using namespace std;
 class B{
    public:
       B(){
          cout<<"B()\n";
       }
       virtual ~B(){ 
          cout<<"~B()\n";
       }
 };
 class D: public B{
    public:
       D(){
          cout<<"D()\n";
       }
       ~D(){
          cout<<"~D()\n";
       }
 };
 int main(){
    B *b = new D();
    delete b;
    return 0;
 }

OUTPUT:
B()
D()
~D()
~B()

==============
If you don't give ~B()  as virtual. then output would be 
B()
D()
~B()
where destruction of ~D() is not done which leads to leak

6
ответ дан Prakash GiBBs 27 August 2018 в 01:11
поделиться

Любой класс, который наследуется публично, полиморфно или нет, должен иметь виртуальный деструктор. Иными словами, если на него может указывать указатель на базовый класс, его базовый класс должен иметь виртуальный деструктор.

Если виртуальный, вызывается деструктор производного класса, то конструктор базового класса. Если не виртуально, вызывается только деструктор базового класса.

-1
ответ дан Syed H 27 August 2018 в 01:11
поделиться

Деструкторы виртуального базового класса - «лучшая практика» - вы всегда должны использовать их, чтобы избежать (трудно обнаружить) утечки памяти. Используя их, вы можете быть уверены, что все деструкторы в цепочке наследования ваших классов вызываются (в правильном порядке). Наследование из базового класса с использованием виртуального деструктора делает деструктор наследующего класса автоматически виртуальным (так что вам не нужно повторно указывать «виртуальный» в объявлении деструктора класса наследования).

2
ответ дан Trantor 27 August 2018 в 01:11
поделиться

, когда вам нужно вызвать деструктор производного класса из базового класса. вам нужно объявить деструктор виртуального базового класса в базовом классе.

0
ответ дан user2641018 27 August 2018 в 01:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: