我了解虚析构函数的需要。 但是,为什么我们需要一个纯虚析构函数? 在C ++的文章之一,笔者已经提到,我们用纯虚析构函数,当我们想使一个抽象类。
但是,我们可以通过做任何的成员函数为纯虚使抽象的类。
所以我的问题是
我们什么时候真正使析构函数纯虚? 任何人都可以提供一个良好的实时的例子吗?
当我们创建抽象类是一个很好的做法,使析构函数也是纯虚? 如果yes..then为什么?
我了解虚析构函数的需要。 但是,为什么我们需要一个纯虚析构函数? 在C ++的文章之一,笔者已经提到,我们用纯虚析构函数,当我们想使一个抽象类。
但是,我们可以通过做任何的成员函数为纯虚使抽象的类。
所以我的问题是
我们什么时候真正使析构函数纯虚? 任何人都可以提供一个良好的实时的例子吗?
当我们创建抽象类是一个很好的做法,使析构函数也是纯虚? 如果yes..then为什么?
可能是纯虚析构函数被允许的真正原因是,禁止他们将意味着添加另一条规则的语言和有没有必要对这个规则,因为没有不良影响可能来自允许纯虚析构函数。
不,普通的旧虚拟就够了。
如果其虚拟方法创建的默认实现的目标,并希望把它抽象不强迫任何人凌驾于任何具体的方法,可以使析构函数纯虚。 我没有看到多少点,但它是可能的。
注意,因为编译器会为派生类的析构函数隐含的,如果类的作者不这样做,任何派生类不会是抽象的。 因此具有在基类中的纯虚拟析构函数将没有任何区别的派生类。 这只会让基类的抽象(感谢@kappa的评论)。
你也可以认为每一个派生类将可能需要有具体的清理代码,并使用纯虚析构函数,提醒自己写一个但这似乎做作(和非强制)。
注意:析构是,即使它是纯虚必须具有以实例化的派生类的实现(是纯虚函数可以具有实现)的唯一方法。
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.
};
所有你需要为一个抽象类,至少是一个纯虚函数。 所有功能都行; 可是事实上,析构函数是什么, 任何类都会有,所以它总是在那里为候选人。 此外,使析构函数纯虚(而不仅仅是虚拟的)具有比其他使抽象类无行为副作用。 因此,很多风格指南的建议纯虚destuctor一致地用来表示一类是抽象的,如果不是它提供了一个一致的地方的人阅读的代码可以看看,看是否是一个抽象类没有其他原因。
如果你想创建一个抽象基类:
......这是最简单的通过使析构函数纯虚并为它提供一个定义(方法体),使抽象类。
对于我们假设ABC:
你保证它不能被实例化(甚至内部类本身,这就是为什么私有的构造可能不够),你得到你想要的析构函数虚拟行为,你没有发现和标记是没有按另一种方法“T需要虚拟调度‘虚拟’。
从我读过你的问题的答案,我不能推导出一个很好的理由实际使用纯虚析构函数。 例如,由于以下原因,不相信我在所有:
可能是纯虚析构函数被允许的真正原因是,禁止他们将意味着添加另一条规则的语言和有没有必要对这个规则,因为没有不良影响可能来自允许纯虚析构函数。
在我看来,纯虚析构函数可能是有用的。 例如,假设你有两个类myClassA和myClassB在你的代码,并从myClassB继承myClassA。 对于斯科特迈尔斯在他的著作“更有效的C ++”,33项“让非叶类抽象”中提到的原因,更好的做法是真正创建一个抽象类myAbstractClass从myClassA和myClassB继承。 这提供了更好的抽象,并防止有,例如,对象副本所产生的一些问题。
在(的创建类myAbstractClass)抽象过程,它可以是没有myClassA或myClassB的方法是,作为一个纯虚方法(这是myAbstractClass是抽象的先决条件)的良好候选。 在这种情况下,您可以定义抽象类的析构函数纯虚。
此后从一些代码,我自己编写一个具体的例子。 我有两个班或数字/ PhysicsParams共享公共属性。 所以我让他们从抽象类继承IParams。 在这种情况下,我在手上绝对没有方法可以是纯粹虚拟的。 该方法的setParameter,例如,必须有相同的身体每个子类。 我有过唯一的选择就是让IParams'析构函数纯虚。
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;
};
如果你想阻止基类的实例化未做您已经实施和测试派生类中的任何改变,你实现你的基类中的纯虚析构函数。
在这里我想告诉当我们需要虚析构函数 ,当我们需要纯虚析构函数
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
}
当你想,没有人应该能够直接创建基类的对象,请使用纯虚析构函数virtual ~Base() = 0
。 通常在-至少需要一个纯虚函数,让我们virtual ~Base() = 0
,因为该功能。
当你不需要上面的事情,只需要派生类对象的安全销毁
基* PBASE =新的派生(); 删除PBASE; 不要求纯虚析构函数,只有虚析构函数将做的工作。
你做了这些答案hypotheticals,所以我会尽量做一个更简单,更脚踏实地解释清楚的原因。
面向对象设计的基本关系是两个:IS-A和HAS-A。 我没有做这些了。 这是他们叫什么。
IS-A指示特定对象识别为是在它上面的一个类层次结构的类。 香蕉对象是水果对象,如果它是水果类的子类。 这意味着,任何地方的水果类可以使用,香蕉都可以使用。 这不是反身,虽然。 如果特定类叫做你不能代替基类特定类。
具有-a所示,一个对象是一个复合类的一部分,并且有一个所有权关系。 这意味着在C ++中,这是一个部件对象,因此该责任是所属类本身破坏前处置,或手所有权关闭。
这两个概念是不是像C ++中的多重继承模型更在单继承语言来实现,但规则基本上是相同的。 并发症时,如果本类身份是模糊的,如传递一个香蕉类指针成接受一个水果类的指针的函数。
虚拟函数是,首先,运行时的事情。 这是多态的,因为它是用来决定哪些功能是在它被称为在运行程序时运行的一部分。
virtual关键字是绑定函数以某一顺序是否存在关于类身份歧义编译器指令。 虚函数总是在父类(据我所知),并表示到他们的名字的成员函数结合应该发生与子类功能首先和后父类的函数,编译器。
一个水果类可以有一个虚拟函数的颜色(),默认情况下返回“NONE”。 香蕉类颜色()函数返回“黄色”或“棕色”。
但是,如果采取了水果指针的函数调用发送给它的香蕉类颜色() - 其颜色()函数被调用? 该功能通常会调用水果::颜色()的水果对象。
这将的99%的时间不想要的结果。 但是,如果水果::颜色()被宣布虚拟然后香蕉:颜色()将呼吁对象,因为正确的颜色()函数将在调用的时候被绑定到水果指针。 运行时将检查哪些对象的指针所指向的,因为它标志着在水果类定义的虚拟。
这比在子类中重写的功能不同。 在这种情况下,水果指针会调用水果::颜色(),如果所有它知道的是,这是-A指针水果。
所以,现在的“纯虚函数”的理念上来。 这是一个相当不幸的短语纯度无关,用它做。 这意味着,其意图是基类方法是从来没有被调用。 事实上,一个纯虚函数不能被调用。 它仍然必须无论如何定义。 必须存在一个函数签名。 许多程序员创建一个空的实现{}的完整性,但是编译器会如果没有在内部生成一个。 在当函数被调用,即使指针是水果,香蕉这种情况下::颜色()将被调用,因为它是彩色的唯一实现()有。
现在,最后一块拼图:构造函数和析构函数。
纯虚构造函数是非法的,完全。 这是刚刚走出。
但纯虚析构函数做要禁止一个基类实例的创建的情况下工作。 只有子类可以如果基类的析构函数是纯虚被实例化。 惯例是将其分配到0。
virtual ~Fruit() = 0; // pure virtual
Fruit::~Fruit(){} // destructor implementation
你必须建立在这种情况下实现。 编译器知道这是你在做什么,并确保你做对了,还是这大大地抱怨说,它不能链接到它需要编译的所有功能。 如果你是不是在正确的轨道,以你是如何建模的类层次结构上的错误可能会造成混淆。
所以,你被禁止在这种情况下,创建水果的实例,但不允许创建香蕉的实例。
调用删除水果指针指向香蕉的实例会调用香蕉::〜香蕉()第一,然后调用Fuit ::〜水果(),始终。 因为无论什么时候,当你调用子类的析构函数,基类的析构函数必须遵循。
它是一个坏榜样? 这是在设计阶段比较复杂,是的,但它可以确保正确的链接是在运行时间和执行功能的子类,其中有歧义,究竟正在访问该子类进行。
如果你写C ++,所以你只围绕确切类指针传递没有通用也不含糊指针,那么是不是真的需要虚函数。 但是,如果你需要的类型运行时的灵活性(如苹果香蕉橘子==>水果)的功能变得更简单,更通用的用更少的冗余代码。 您不必再编写每种水果的功能,你知道,每一个水果会以颜色()有自己正确的响应函数。
我希望这长篇大论的解释凝固的概念,而不是混淆的东西。 有很多很好的例子在那里看一下,看够了,实际上他们跑他们的混乱,你会得到它。
你问一个例子,我相信下面提供了一个纯虚析构函数的一个原因。 我期待着答复,这是否是一个很好的理由...
我不希望任何人能够抛出error_base
类型,但异常类型error_oh_shucks
和error_oh_blast
具有相同的功能,我不想把它写两次。 平普尔复杂性是必要的,以避免暴露std::string
我的客户,以及使用std::auto_ptr
必要拷贝构造函数。
公共头包含异常规范,将提供给客户,以区分不同类型的异常由我的图书馆被抛出:
// 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; }
这里是共享的实现:
// 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();
}
该exception_string类,保密的,从我的公共接口隐藏的std :: string:
// 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_;
};
然后我的代码抛出一个错误:
#include "error.h"
throw error<error_oh_shucks>("That didn't work");
使用了模板的error
有点没来由。 它保存在客户需要捕获错误作为牺牲一些代码:
// client.cpp
#include <error.h>
try {
} catch (const error<error_oh_shucks>&) {
} catch (const error<error_oh_blast>&) {
}
也许有,我居然无法在其他的答案看纯虚析构函数的另一个REAL用例 :)
起初,我完全有明显的答案一致认为:这是因为禁止纯虚析构函数需要在语言规范的额外的规则。 但它仍然不能使用的情况下,马克呼吁:)
首先想象一下:
class Printable {
virtual void print() const = 0;
// virtual destructor should be here, but not to confuse with another problem
};
并且是这样的:
class Printer {
void queDocument(unique_ptr<Printable> doc);
void printAll();
};
简单-我们有接口Printable
和一些“容器”拿着这个接口什么。 我觉得这里很清楚,为什么print()
方法是纯虚。 它可以有一些身体但如果没有默认的实现,纯虚是一个理想的“执行”(=“必须由子类中提供”)。
现在想象完全一样,除了它不是打印,但销毁:
class Destroyable {
virtual ~Destroyable() = 0;
};
也有可能是一个类似容器:
class PostponedDestructor {
// Queues an object to be destroyed later.
void queObjectForDestruction(unique_ptr<Destroyable> obj);
// Destroys all already queued objects.
void destroyAll();
};
它从我的实际应用简化的用例。 这里唯一的区别是,“特殊”的方法(析构函数)来代替“正常” print()
但为什么它是纯虚的原因仍然是相同的 - 没有为方法没有默认代码。 有点混乱可能是事实,必须有有效的一些析构函数和编译器实际上它生成一个空码。 但是,从程序员的角度纯虚拟性仍然意味着:“我没有任何的默认代码,它必须由派生类来提供。”
我认为这是没有什么大的想法在这里,只是更多的解释是纯粹的虚拟性作品真的统一 - 也析构函数。
这是十年的老话题了:)阅读“有效的C ++”一书最近5个段落编号7的细节,是从“偶尔它可以方便的给类纯虚析构函数......”
1)当你想要求派生类做清理。 这是罕见的。
2)没有,但你希望它是虚拟的,但。
我们需要做的事实,析构函数虚拟bacause的是,如果我们不进行析构函数虚拟那么编译器只会破坏基类的内容,正所有的派生类将保持未改变,bacuse编译器将不能调用其他的析构函数除了基类类。