Skip to content

Latest commit

 

History

History
456 lines (322 loc) · 19.4 KB

18.7 — Pure virtual functions, abstract base classes, and interface classes.md

File metadata and controls

456 lines (322 loc) · 19.4 KB

18.7 — Pure virtual functions, abstract base classes, and interface classes

Pure virtual (abstract) functions and abstract base classes

So far, all of the virtual functions we have written have a body (a definition). However, C++ allows you to create a special kind of virtual function called a pure virtual function (or abstract function) that has no body at all! A pure virtual function simply acts as a placeholder that is meant to be redefined by derived classes.

到目前为止,我们写的每个虚函数都有一个函数体。然而C++允许你创建一个特别类型的虚函数。叫做纯虚函数或者叫抽象函数。这个函数没有函数体,一个纯虚函数就是一个位置,为了让派生类来填的位置。

To create a pure virtual function, rather than define a body for the function, we simply assign the function the value 0.

创建一个纯虚函数,不要定义函数体,给这个函数赋值0就可以。

class Base
{
public:
    const char* sayHi() const { return "Hi"; } // a normal non-virtual function    
 
    virtual const char* getName() const { return "Base"; } // a normal virtual function
 
    virtual int getValue() const = 0; // a pure virtual function
 
    int doSomething() = 0; // Compile error: can not set non-virtual functions to 0
};

When we add a pure virtual function to our class, we are effectively saying, “it is up to the derived classes to implement this function”.

当我们添加一个纯虚函数的时候,我们实际上实在说,取决于派生类来实现这个函数。

Using a pure virtual function has two main consequences: First, any class with one or more pure virtual functions becomes an abstract base class, which means that it can not be instantiated! Consider what would happen if we could create an instance of Base:

使用纯虚函数有两个主要的结果:第一,任何拥有一个或者多个纯虚函数的类叫做抽象基类。这意味着这个类不能被实例化。思考一下下面如果我们要创建一个Base的实例的话会怎么样:

int main()
{
    Base base; // We can't instantiate an abstract base class, but for the sake of example, pretend this was allowed
    base.getValue(); // what would this do?
 
    return 0;
}

Because there’s no definition for getValue(), what would base.getValue() resolve to?

因为没有getValue的定义,那么基类的getValue方法怎么处理呢?

Second, any derived class must define a body for this function, or that derived class will be considered an abstract base class as well.

第二件事情是,任何基于此的派生类必须实现这个函数,不然的话,派生类也会被认为是抽象类。

A pure virtual function example

Let’s take a look at an example of a pure virtual function in action. In a previous lesson, we wrote a simple Animal base class and derived a Cat and a Dog class from it. Here’s the code as we left it:

让我们看一下纯虚函数的例子。在前面的课程中,我们写了一个动物类和一个猫一个狗三个类。

#include <string>
#include <utility>
 
class Animal
{
protected:
    std::string m_name;
 
    // We're making this constructor protected because
    // we don't want people creating Animal objects directly,
    // but we still want derived classes to be able to use it.
    Animal(const std::string& name)
        : m_name{ name }
    {
    }
 
public:
    std::string getName() const { return m_name; }
    virtual const char* speak() const { return "???"; }
    
    virtual ~Animal() = default;
};
 
class Cat: public Animal
{
public:
    Cat(const std::string& name)
        : Animal{ name }
    {
    }
 
    const char* speak() const override { return "Meow"; }
};
 
class Dog: public Animal
{
public:
    Dog(const std::string& name)
        : Animal{ name }
    {
    }
 
    const char* speak() const override { return "Woof"; }
};

We’ve prevented people from allocating objects of type Animal by making the constructor protected. However, there are two problems with this:

我们已经阻止别人使用通过构造函数构造Animal的实例了,然而这样做有两个问题:

  1. The constructor is still accessible from within derived classes, making it possible to instantiate an Animal object.

这个构造函数在派生类中依然还可以访问,使得实例化一个Animal对象成为可能。

  1. It is still possible to create derived classes that do not redefine function speak().

创建一个不重定义speak函数的派生类依然是有可能的(打印一个???)。

For example:

#include <iostream>
#include <string>
 
class Cow : public Animal
{
public:
    Cow(const std::string& name)
        : Animal{ name }
    {
    }
 
    // We forgot to redefine speak
};
 
int main()
{
    Cow cow{"Betsy"};
    std::cout << cow.getName() << " says " << cow.speak() << '\n';
 
    return 0;
}

This will print:

Betsy says ???

What happened? We forgot to redefine function speak(), so cow.Speak() resolved to Animal.speak(), which isn’t what we wanted.

发生了的什么,我们忘了重定义一个speak的函数,所以牛叫的时候被解析为动物叫,这实际上不是我们想要的。

A better solution to this problem is to use a pure virtual function:

解决这个问题的更好办法是使用纯虚函数。

#include <string>
 
class Animal // This Animal is an abstract base class
{
protected:
    std::string m_name;
 
public:
    Animal(const std::string& name)
        : m_name{ name }
    {
    }
 
    const std::string& getName() const { return m_name; }
    virtual const char* speak() const = 0; // note that speak is now a pure virtual function
    
    virtual ~Animal() = default;
};

There are a couple of things to note here. First, speak() is now a pure virtual function. This means Animal is now an abstract base class, and can not be instantiated. Consequently, we do not need to make the constructor protected any longer (though it doesn’t hurt). Second, because our Cow class was derived from Animal, but we did not define Cow::speak(), Cow is also an abstract base class. Now when we try to compile this code:

这里有一些事情要注意。首先,speak现在是一个纯虚函数,这意味着Animal现在是一个抽象基类,不能被实例化。因此我们不需要使者构造函数用protected来限定,尽管这样做也没什么不好。再就是,因我们的Cow类是从Animal派生出来的,但是我们没有定义speak函数,所以现在Cow也被认为是一个抽象基类了。我们现在尝试编译下面的代码:

#include <iostream>
 
class Cow: public Animal
{
public:
    Cow(const std::string& name)
        : Animal{ name }
    {
    }
 
    // We forgot to redefine speak
};
 
int main()
{
    Cow cow{ "Betsy" };
    std::cout << cow.getName() << " says " << cow.speak() << '\n';
 
    return 0;
}

The compiler will give us a warning because Cow is an abstract base class and we can not create instances of abstract base classes:

这个编译器会给我们一个警告,因为Cow是一个抽象基类,我们不能创建抽象基类的对象。

C:\Test.cpp(141) : error C2259: 'Cow' : cannot instantiate abstract class due to following members:
        C:Test.cpp(128) : see declaration of 'Cow'
C:\Test.cpp(141) : warning C4259: 'const char *__thiscall Animal::speak(void)' : pure virtual function was not defined

This tells us that we will only be able to instantiate Cow if Cow provides a body for speak().

这告诉我们只有在Cow提供了一个函数体的情况下才能实例化Cow对象。

Let’s go ahead and do that:

#include <iostream>
#include <string>
 
class Cow: public Animal
{
public:
    Cow(const std::string& name)
        : Animal(name)
    {
    }
 
    const char* speak() const override { return "Moo"; }
};
 
int main()
{
    Cow cow{ "Betsy" };
    std::cout << cow.getName() << " says " << cow.speak() << '\n';
 
    return 0;
}

Now this program will compile and print:

Betsy says Moo

A pure virtual function is useful when we have a function that we want to put in the base class, but only the derived classes know what it should return. A pure virtual function makes it so the base class can not be instantiated, and the derived classes are forced to define these functions before they can be instantiated. This helps ensure the derived classes do not forget to redefine functions that the base class was expecting them to.

一个纯虚函数是非常有用的,在我们想在基类中定义,在派生类中实现的时候就可以这么做。一个纯虚函数使得那个类不能被实例化,派生类也被强制要求实现那个函数,不然的话就不能实例化而也成为一个抽象基类。这帮助我们保证了派生类不会忘记重新定义基类想让我们实现的函数。

Pure virtual functions with bodies

It turns out that we can define pure virtual functions that have bodies:

我们可以给纯虚函数定义一个函数体。

#include <string>
class Animal // This Animal is an abstract base class
{
protected:
    std::string m_name;
 
public:
    Animal(const std::string& name)
        : m_name{ name }
    {
    }
 
    std::string getName() { return m_name; }
    virtual const char* speak() const = 0; // The = 0 means this function is pure virtual
    
    virtual ~Animal() = default;
};
 
const char* Animal::speak() const  // even though it has a body
{
    return "buzz";
}

In this case, speak() is still considered a pure virtual function because of the “=0” (even though it has been given a body) and Animal is still considered an abstract base class (and thus can’t be instantiated). Any class that inherits from Animal needs to provide its own definition for speak() or it will also be considered an abstract base class.

在本例中,我们把speak定义为了一个纯虚函数,Animal这个类仍然被认为是抽象基类,所以也不能被实例化。任何从Animal类继承的类都需要提供他们自己实现的函数定义,不然的话就被认为是一个抽象基类。

When providing a body for a pure virtual function, the body must be provided separately (not inline).

在提供了一个函数体给纯虚函数的情况下,这个函数体必须被分开。(不能直接像之前那样写在一行)

For Visual Studio users

Visual Studio mistakenly allows pure virtual function declarations to be definitions, for example

// wrong!
virtual const char* speak() const = 0
{
  return "buzz";
}
This is wrong and cannot be disabled.

This paradigm can be useful when you want your base class to provide a default implementation for a function, but still force any derived classes to provide their own implementation. However, if the derived class is happy with the default implementation provided by the base class, it can simply call the base class implementation directly. For example:

这个范例在你想让你的基类提供一个默认的函数实现的时候是有用的,但他依然强制要求任何派生类提供他们自己的实现。然而,如果一个派生类对基类的默认实现感到满意,他可以直接调用基类提供的函数实现,像下面这样:

#include <string>
#include <iostream>
 
class Animal // This Animal is an abstract base class
{
protected:
    std::string m_name;
 
public:
    Animal(const std::string& name)
        : m_name(name)
    {
    }
 
    const std::string& getName() const { return m_name; }
    virtual const char* speak() const = 0; // note that speak is a pure virtual function
    
    virtual ~Animal() = default;
};
 
const char* Animal::speak() const
{
    return "buzz"; // some default implementation
}
 
class Dragonfly: public Animal
{
 
public:
    Dragonfly(const std::string& name)
        : Animal{name}
    {
    }
 
    const char* speak() const override// this class is no longer abstract because we defined this function
    {
        return Animal::speak(); // use Animal's default implementation
    }
};
 
int main()
{
    Dragonfly dfly{"Sally"};
    std::cout << dfly.getName() << " says " << dfly.speak() << '\n';
 
    return 0;
}

The above code prints:

Sally says buzz

This capability isn’t used very commonly.

这种能力并不是很常用。

Interface classes 接口类

An interface class is a class that has no member variables, and where all of the functions are pure virtual! In other words, the class is purely a definition, and has no actual implementation. Interfaces are useful when you want to define the functionality that derived classes must implement, but leave the details of how the derived class implements that functionality entirely up to the derived class.

**一个接口类是没有成员变量的,并且所有的函数都是纯虚函数。**换句话说,这个类纯粹就只有定义,没有实质上的实现。在你想定义派生类必须实现的功能的时候,把所有的实现内容交给派生类决定的时候,接口是有用的。

Interface classes are often named beginning with an I. Here’s a sample interface class:

接口类的名称经常以I打头。下面是一个接口类的例子:

class IErrorLog
{
public:
    virtual bool openLog(const char *filename) = 0;
    virtual bool closeLog() = 0;
 
    virtual bool writeError(const char *errorMessage) = 0;
 
    virtual ~IErrorLog() {} // make a virtual destructor in case we delete an IErrorLog pointer, so the proper derived destructor is called
};

Any class inheriting from IErrorLog must provide implementations for all three functions in order to be instantiated. You could derive a class named FileErrorLog, where openLog() opens a file on disk, closeLog() closes the file, and writeError() writes the message to the file. You could derive another class called ScreenErrorLog, where openLog() and closeLog() do nothing, and writeError() prints the message in a pop-up message box on the screen.

**任何继承了这个接口类的派生类必须实现所有的函数才能被实例化。**你可以派生一个类叫FileErrorLog,openLog函数打开一个磁盘上的文件,closeLOg把这个文件关掉,writeError给文件里面写一些信息。你可以派生另一个类叫ScreenErrorLog,openLog和closeLog什么都不做,而wirteError把错误信息写到弹出来的消息框里

Now, let’s say you need to write some code that uses an error log. If you write your code so it includes FileErrorLog or ScreenErrorLog directly, then you’re effectively stuck using that kind of error log (at least without recoding your program). For example, the following function effectively forces callers of mySqrt() to use a FileErrorLog, which may or may not be what they want.

现在你假设你需要写一下代码用这个错误日志类。如果你的代码直接的引入FileErrorLog或者是ScreenErrorLog,你就被具体的错误日志类绑死了。举个例子,下面的的函数使用一个文件错误日志对象,可能不是他们想要的。

#include <cmath> // for sqrt()
 
double mySqrt(double value, FileErrorLog &log)
{
    if (value < 0.0)
    {
        log.writeError("Tried to take square root of value less than 0");
        return 0.0;
    }
    else
    {
        return std::sqrt(value);
    }
}

A much better way to implement this function is to use IErrorLog instead:

一个更好的办法是使用IErrorLog来作为参数

#include <cmath> // for sqrt()
double mySqrt(double value, IErrorLog &log)
{
    if (value < 0.0)
    {
        log.writeError("Tried to take square root of value less than 0");
        return 0.0;
    }
    else
    {
        return std::sqrt(value);
    }
}

Now the caller can pass in any class that conforms to the IErrorLog interface. If they want the error to go to a file, they can pass in an instance of FileErrorLog. If they want it to go to the screen, they can pass in an instance of ScreenErrorLog. Or if they want to do something you haven’t even thought of, such as sending an email to someone when there’s an error, they can derive a new class from IErrorLog (e.g. EmailErrorLog) and use an instance of that! By using IErrorLog, your function becomes more independent and flexible.

现在调用者可以传入任何从IErrorLog派生的类的对象。如果他们想把错误打印到文件,那么就传入一个文件日志对象的接口类引用。如果想打印到屏幕上,那么就传入一个屏幕错误日志类的接口类引用。假设如果别人想做一些你从来没想过的事情,比如发送一个email给某个人,他们可以从这个接口类派生一个新的类比如说是EmailErrorLog类,然后使用这个类的实现传入我们的mysql函数。通过使用这个接口类,你的函数功能变得更独立和更具灵活。

Don’t forget to include a virtual destructor for your interface classes, so that the proper derived destructor will be called if a pointer to the interface is deleted.

不要忘了引入一个虚的析构函数给你的接口类,这样的话在一个指针被删除的时候,对应的合适的析构函数才能被调用。

Interface classes have become extremely popular because they are easy to use, easy to extend, and easy to maintain. In fact, some modern languages, such as Java and C#, have added an “interface” keyword that allows programmers to directly define an interface class without having to explicitly mark all of the member functions as abstract. Furthermore, although Java (prior to version 8) and C# will not let you use multiple inheritance on normal classes, they will let you multiple inherit as many interfaces as you like. Because interfaces have no data and no function bodies, they avoid a lot of the traditional problems with multiple inheritance while still providing much of the flexibility.

接口类已经是极其流行的了。因为他们很易用,容易扩展,而且容易维护,事实上,一些现代的语言,比如Java和C#,已经增加了interface关键字,允许程序员直接定义一个接口类而不需要明确的标记所有的成员函数是抽象的,此外,尽管Java和C#不会允许你使用多继承在普通的类上,但是他们允许接口的多继承。因为接口没有数据也没有函数体,所以他们这样做能在避开很多多继承带来的问题的同事,有更多的灵活性。

Pure virtual functions and the virtual table 纯虚函数和虚表

Abstract classes still have virtual tables, as these can still be used if you have a pointer or reference to the abstract class. The virtual table entry for a pure virtual function will generally either contain a null pointer, or point to a generic function that prints an error (sometimes this function is named __purecall) if no override is provided.

抽象类也有虚表,因为他们在你有一个基类指针指向派生类的时候依然起作用,所以肯定要用到虚表。在没有重写提供的情况下,虚表中的纯虚函数对应的内容是一个空指针,或是指向一个普通函数打印一个错误(有时候这个函数叫做__purecall)。