/*此文是译者出于自娱翻译的GotW(Guru of the Week:http://www.gotw.ca/gotw/index.htm)系列文章的一篇,原文的版权是属于Hub Sutter(著名的C++专家,《Exceptional C++》的作者)。此文的翻译没有征得原作者的同意,只供学习讨论。——译者:黄森堂*/
#31 纯虚函数.
难度:7/10
产生有意义的纯虚函数仍需要提供实现部分吗?
问题:
JG问题
1.什么是纯虚函数?,给个示例.
Guru问题
2.为什么定义纯虚函数也要写实现部分吗?,给些理由。
解决方法:
1.什么是纯虚函数?,给个示例.
纯虚函数是虚函数,它需要你的派生类强制去覆盖,如果派生类没有覆盖任何基类的纯虚函数,它是个抽象类,你不能创建对象.
class AbstractClass {
public:
// 这义纯虚函数:
// 这个类是抽象类
virtual void f(int) = 0;
};
class StillAbstract : public AbstractClass {
// 没有覆盖f(int),
// 也是抽象类
};
class Concrete : public StillAbstract {
public:
// 覆盖f(int),
// 这个类是实体类
void f(int) { /*...*/ }
};
AbstractClass a; // 错误,抽象类
StillAbstract b; // 错误,抽象类
Concrete c; // OK,实体类
2.为什么定义纯虚函数也要写实现部分吗?,给些理由。
There are three main reasons you might do this. #1 is commonplace, #2 is pretty rare, and #3 is a workaround used occasionally by advanced programmers working with weaker compilers.
Most programmers should only ever use #1.
1.纯析构函数
所有的基类将都需要纯析构函数(C++书上有说明)。如果类是抽象(防止创建它),但它不产生于其它纯虚函数里,公共方法是产生纯析构函数:
// file b.h
class B {
public: /*...other stuff...*/
virtual ~B() = 0; // 纯析构函数
};
任何派生类都必须调用基类的析构,同样析构函数仍然需要定义(即使是空的):
// file b.cpp
B::~B() { /* 空possibly empty */ }
如果这儿不提这个空定义,那么你仍然要从B派生的话,你是不用它创建实例的,它们是无效的。
2.接受默认行为
如果派生类不选择覆盖普通虚函数,它仅仅继承基类的默认行为,如何你提供了默认行为,但不让派生类只是仅仅地继承它,你可以产生纯虚函数,但在派生类中仍然需要提供实体:
class B {
public:
virtual bool f() = 0;
};
bool B::f() {
return true; // this is a good default, but
} // shouldn't be used blindly
class D : public B {
public:
bool f() {
return B::f(i); // if D wants the default
} // behaviour, it has to say so
};
3. Workaround Poor Compiler Diagnostics(无法理解该段文字的意思,没有翻译)
There are situations where you could accidentally end up calling a pure virtual function (indirectly from a base constructor or destructor; see your favourite advanced C++ book for examples). Of course, well-written code normally won't get into these problems, but no one's perfect and once in a while it happens.
Unfortunately, not all compilers[1] will actually tell you when this is the problem. Those that don't can give you spurious unrelated errors that take forever to track down. "Argh," you scream, when you do finally diagnose the problem yourself some hours later, "why didn't the compiler just tell me that's what I did?!"
One way to protect yourself against this wasted debugging time is to provide definitions for the pure virtual functions that should never be called, and put some really evil code into those definitions that lets you know right away if you do call them accidentally. For example:
class B {
public:
virtual bool f() = 0;
};
bool B::f() { // this should NEVER be called
if( PromptUser( "pure virtual B::f called -- "
"abort or ignore?" ) == Abort )
DieDieDie();
}
In the common DieDieDie() function, do whatever is necessary on your system to get into the debugger or dump a stack trace or otherwise get diagnostic information. Here are some common methods that will get you into the debugger on most systems. Pick the one that you like best.
void DieDieDie() { // scribble through null ptr
memset( 0, 1, 1 );
}
void DieDieDie() { // another C-style method
assert( false );
}
void DieDieDie() { // back to last "catch(...)"
class LocalClass {};
throw LocalClass();
}
void DieDieDie() { // for standardphiles
throw logic_error();
}
You get the idea. Be creative. :-)
Notes
1. Well, technically it's the runtime environment that catches this sort of thing. I'll say "compiler" anyway because it's generally the compiler that ought to slip in the code that checks this for you at runtime.