什么是使类抽象的好处

时间:2013-04-17 11:14:06

标签: c++ abstract-class

我们举一个例子,

class base{
public:
virtual void abstract() = 0;
};

class derived:public base{
public:
void abstract(){cout << "Abstract\n";}
};

int main{
derived d;
d.abstract();
return 0;
}

可以用其他方式编写,

class base{
    public:
    void abstract(){cout << "Abstract\n";}
    };

    int main{
    base b;
    b.abstract();
    return 0;
    }

它也提供相同的结果,事实上我不需要派生类。 我确实阅读了很多关于抽象类的文章it says we can not instantiate base classpure virtual function强制用户define the function。 但是如果我们在两种情况下都会看到上面的代码,那么我得到相同的结果(or output)。 所以我的问题是abstract类如何帮助我们?

7 个答案:

答案 0 :(得分:6)

正如您所注意到的,在您的示例代码中,没有必要具有单独的基类和派生类。

一般来说,,具有虚函数的类的目的是动态多态,但是你的代码没有使用它。

假设您有一些代码使用base*而不知道它实际指向的(几个)派生类。假设它们各自具有abstract()的不同实现。假设你想强制任何编写base派生类的人来实现他们自己的abstract()版本。然后有理由让abstract()成为纯虚函数,因此base成为抽象类的原因是:

#include <iostream>
#include <cstdlib>
#include <ctime>

struct Base {
    virtual void abstract() = 0;
    virtual ~Base() {};
};

struct Derived1 : Base {
    void abstract() { std::cout << "Derived1\n"; }
};

struct Derived2 : Base {
    void abstract() { std::cout << "Derived2\n"; }
};

Base *random_object() {
    if (std::rand() < RAND_MAX/2) {
        return new Derived1();
    } else {
        return new Derived2();
    }
}

int main() {
    std::srand(std::time(0));
    Base *b = random_object();
    b->abstract();
    delete b;
}

main中的代码只关心Base,除了可能存在某些内容之外,它不需要知道派生类的任何内容。只有random_object中的代码才知道派生类。同时Base不需要知道如何实现abstract(),只需要派生类(并且每个人只关心它自己的实现)。对于不需要了解事物的代码来说这很好 - 这意味着可以在不触及不关心的代码的情况下更改这些内容。

使用非抽象类作为公共基类也存在一些高级设计问题。例如,一旦你的一些函数被覆盖,很容易不经意地编写代码就会无法正常工作,并且很容易意外地用一个具体的基类切片一个对象。因此,有一种哲学认为你应该随时都知道你是在写一个具体的课程还是一个基础课程,而不是试图同时做两件事。当你遵循这个哲学时,所有基类都是抽象的,所以你创建一个抽象类来表示它是设计为派生的。

答案 1 :(得分:3)

抽象类用于为某些功能提供接口,其中确切的行为由调用者的类定义。要确保函数的“用户”实现其功能,请在基类中使用抽象函数。这样,如果“用户”没有提供所需的功能之一,则代码不会编译。

答案 2 :(得分:2)

以下是您的示例:假设您的程序需要计算不同形状的区域。

class shape{
public:
    virtual double area() = 0;
};

class triangle:public shape{
public:
    triangle(double base, double height) : b(base), h(height) {}
    double area(){ return 0.5 * b * h; }
private:
    double b;
    double h;
};

class circle:public shape{
public:
    circle(double radius) : r(radius) {}
    double area(){ return M_PI * r * r; }
private:
    double r;
};

int main{
    std::vector<shape*> shapes;

    //add whatever shapes you want here
    shapes.push_back( new triangle(4, 5) );
    shapes.push_back( new circle(3) );

    double dTotal = 0.0;
    std::vector<shape*>::iterator i;
    for (i = shapes.begin(); i != shapes.end(); i++)
    {
        dTotal += (*i)->area();
        delete *i;
    }

    cout << dTotal;
    return 0;
}

您现在可以非常轻松地为矩形,圆形,十二面体等创建形状,并且在不知道它们如何计算自己区域的具体细节的情况下对它们进行相似处理。同时,shape定义自己的区域是没有意义的。

编辑:添加了另一个派生类,并使用抽象方法使用它们。

答案 3 :(得分:1)

abstract (base-)class的目的是为子类定义interface。通过这种方式,您可以确定您的类必须具备的功能(方法),但不能确定它是如何工作的(实现) 当您从abstract类派生时,您承诺在您的子类中提供某种行为。使用此功能,函数可以使用您的对象并执行其操作,而无需了解其对细节的特定行为 周围有类层次结构,比如.NET - 表示概念的框架或UI控件之类的东西 其次,有一个类的实例有时没有意义。考虑一个班级Animal。如何定义Animal?实例化Animal,而不是从Dog

派生的CatAnimal.是没有意义的

答案 4 :(得分:1)

在“抽象”一词中,抽象类(在完全抽象时也称为 interfaces )从一组类似类的实现中抽象出来。在这里,类似的意思是它们具有相同的功能集,但可能以不同的方式实现它们。

抽象类本身没有定义实现。在程序中传递(指针或引用)抽象类只会传递一个“封闭包”,您可以与之交互(例如摇动)。但是你隐藏了包内的内容,包括界面的实现方式(例如当你摇动它时会发生什么)。

也就是说,抽象类只是将抽象提升一级。类描述了对象具有哪些属性和行为,但是抽象类仅描述了可以对对象执行的操作。只有实际的实现才能定义行为。

这就是说,一个重要的事实是你不能创建一个抽象类的实例。

在设计一个软件时,抽象类/接口是一种非常重要的抽象方法。每当你的某个软件想要与特别不知道的东西进行交互时,你就可以编写一个界面来描述这样一个对象必须提供的功能而不指定实际的类型。这使得可以容易地传递不同类型的对象。对这样的接口/抽象类进行指针/引用的函数可以在不知道其实际类型的情况下与对象进行交互。

答案 5 :(得分:1)

抽象类在您不希望创建类的对象的情况下也很有用。

考虑一下,

/* Abstract Class Animal */
class Animal
{
public:
    virtual void eat() = 0;
    virtual void walk() = 0;
};

class Dog:public Animal
{
public:
    void walk() { std::cout<<"\nWalk with Paws"; }   
    void eat()  { std::cout<<"\nEat meat";       }
};

class Horse:public Animal
{
public:
        void walk() {  std::cout<<"\nWalk with Hooves"; }   
        void eat()  {  std::cout<<"\nEat Grass";        }
};

int main()
{
    Dog d;
    Horse h;

    d.eat();
    d.walk();
    h.eat();
    h.walk();
return 0;
}

在这里,您不希望创建Animal类型的对象。该对象对sleep eat等没有任何特定行为。

您可以利用此abstract类创建类似DogCat等的子类 pure virtual functions必须由派生类实现,否则编译器会给出错误。

这可以保证您的所有子类都具有所需的方法,并且您也不会最终创建undefined对象,例如Animal

答案 6 :(得分:1)

Steve Jessop已经指出了为什么需要抽象类和虚函数。 您可以强制使用vtable来实现动态调度。

您的抽象基类声明派生类的常用功能。 如果在编译时无法确定对象的类型,则虚函数可确保调用正确的函数。

在以下示例中,class other在编译时不知道提供了class shape的派生。 唯一已知的事情是:每个shape衍生物将为center方法提供实现,因为它是 virtual。 (该程序不会编译和链接。) 这足以提供功能。 程序将在运行时确定shPtr的类型,并调用正确的center()

#include <string>
#include <iostream>

using namespace std;

class v2d
{
public: 
  double x, y;
  v2d (void) : x(0.0), y(0.0) { }
  v2d (double const a, double const b) : x(a), y(b) { }
};


class shape
{
public:
  string name;
  shape (void) : name() { }
  shape (string const n) : name(n) { }
  virtual v2d center (void) const = 0;
};


class circle : public shape 
{
private:
  v2d center_point;
  double radius;
public:
  circle (void) : shape("Circle"), center_point(), radius(0.0) { }
  circle (v2d const cp, double const r) : shape("Circle"), center_point(cp), radius(r) { }
  v2d center (void) const { return center_point; }
};

class square : public shape
{
private:
  v2d lowright;
  double sidelength;
public:
  square (void) : shape("Square"), lowright(), sidelength(0.0) { }
  square (v2d const tl, double const sl) : shape("Square"), lowright(tl), sidelength(sl) { }
  v2d center (void) const
  {
    double const halflen = sidelength/2.0;
    return v2d(lowright.x+halflen, lowright.y+halflen);
  }
};


class other
{
private:
  shape *shPtr;
public:
  other (void) : shPtr(NULL) { }
  other (shape *sh_ptr) : shPtr(sh_ptr) { }
  void doSomething (void)
  {
    cout << "Center of this Shape, which is a " << shPtr->name << " is: "<< shPtr->center().x << ", " << shPtr->center().y << endl;
  }
};



int main (void) 
{

  v2d sq_c(1.0, 2.0), circ_c(4.0, 4.0);
  square square_obj(sq_c, 5.0);
  circle circle_obj(circ_c, 2.0);

  other other1 (&square_obj), other2(&circle_obj);

  cout << fixed << setprecision(2);

  other1.doSomething();
  other2.doSomething();

  return 0;
}

输出

Center of this Shape, which is a Square is: 3.50, 4.50
Center of this Shape, which is a Circle is: 4.00, 4.00

完全正确(意味着已经调用了正确的中心函数)。

- 为清晰起见 -

SuvP的动物实例也几乎适合。 它的缺点是不需要继承来实现相同的功能。

我把它调整了一下,试图让纯虚拟的好处变得清晰。 首先,用户可以添加任意Animalfeed_all_animals的实现与当前派生的Animal无关。

#include <string>
#include <iostream>
#include <vector>

using namespace std;

class Animal
{
public:
  virtual string food() = 0;
  virtual string name() = 0; 
  void eat()  { cout << "A " << name() << " is eating " << food() << endl; }
};

class Dog:public Animal
{
public:
  string name() { return "Dog"; }
  string food() { return "Meat"; }
};

class Horse:public Animal
{
public:
  string name() { return "Horse"; }
  string food() { return "Gras"; }
};

void feed_all_animals (vector<Animal*> animals)
{
  for (size_t i=0; i<animals.size(); ++i)
  {
    cout << "Feeding animal " << i+1 << " (a " << animals[i]->name() << ") with " << animals[i]->food() << endl;
    animals[i]->eat();
  }
}

int main (void)
{
  vector<Animal*> animals;
  // We have a zoo with three dogs and two horses
  Dog dog1, dog2, dog3;
  Horse horse1, horse2;
  animals.push_back((Animal*)&dog1);
  animals.push_back((Animal*)&dog2);
  animals.push_back((Animal*)&dog3);
  animals.push_back((Animal*)&horse1);
  animals.push_back((Animal*)&horse2);
  // now we let the user add another animal
  int ani(0);
  cout << "Do you want to add a Horse [1] or a Dog [0]: ";
  cin >> ani;
  switch (ani)
  {
    case 1: animals.push_back((Animal*)new Horse); break;
    default: animals.push_back((Animal*)new Dog); break;
  }
  // so they don't starve
  feed_all_animals(animals);

  delete animals[animals.size()-1];
  animals.clear();
  return 0;
}
相关问题