您的位置:首页 > 编程语言 > C语言/C++

c++ 继承相关注意点

2015-06-27 17:14 295 查看

继承相关知识:

private继承对派生类不可见(exist but not accessable),通过using Base::someFunc可以access基类的成员(前提是someFunc在基类中是public属性).

private 和 protect 继承不允许基类指针指向派生类(编译
error: 'BaseClass' is  an inaccessible base of 'DerivedClass'


reinterpret_cast可以将private继承的派生类指针强制转化为基类指针(通过
pbase = static_cast  <BaseClass*>(pdrived)
;但是不要这么做,因为违反了pravite inheritance的原则!(dynamic_cast 不行,有人说static_cast 也可以,但是我试了不行:gcc4.8.1,c++11)

pravite继承类似于在基类添加了一个private成员变量——聚合(相似,但有区别)尊重原作,原文链接——effective c++ 条款39:

//E.g., the “Car has-a Engine” relationship can be expressed using simple composition
class Engine {
public:
  Engine(int numCylinders);
  void start();                 // Starts this Engine
};
class Car {
public:
  Car() : e_(8) { }             // Initializes this Car with 8 cylinders
  void start() { e_.start(); }  // Start this Car by starting its Engine
private:
  Engine e_;                    // Car has-a Engine
};

//The “Car has-a Engine” relationship can also be expressed using private inheritance:
class Car : private Engine {    // Car has-a Engine
public:
  Car() : Engine(8) { }         // Initializes this Car with 8 cylinders
  using Engine::start;          // Start this Car by starting its Engine
};

//使用 composition 还是 private inheritance?
//Use composition when you can, private inheritance when you have to
//"Isa" relationship should be modelled by inheritance, "has-a" should be modelled by containment:
class B : public A     // B "is-a" A 
{ 
  ... 
} 

class B 
{ 
  ... 
  private: 
    A a_;     // B "has-a" A 
} 
//While public inheritence model an "is-a" relationship,
//private inheritance doesn't model anything at all, 
//and is purely an implementation construct for sharing code with the class being inherited. 
//This is better achieved by containment.


There are several similarities between these two variants:


In both cases there is exactly one Engine member object contained in every Car object

In neither case can users (outsiders) convert a Car* to an Engine*

In both cases the Car class has a start() method that calls the >start() method on the contained Engine object.


There are also several distinctions:


The simple-composition variant is needed if you want to contain several Engines per Car

The private-inheritance variant can introduce unnecessary multiple inheritance

The private-inheritance variant allows members of Car to convert a Car* to an Engine*

The private-inheritance variant allows access to the protected members of the base class

The private-inheritance variant allows Car to override Engine’s virtual functions

The private-inheritance variant makes it slightly simpler (20 characters compared to 28 characters) to give Car a start() method that simply calls through to the Engine’s start() method

Note that private inheritance is usually used to gain access into the protected members of the base class, but this is usually a short-term solution。

- Or A member (either data member or member function) declared in a protected section of a class can only be accessed by member functions and friends of that class, and by member functions and friends of derived classes,这就是为什么private继承可以访问protected的基类成员).

派生类指针转基类指针example: &ojb = this,一般来书dynamic_cast效率比较低,尽量少用。effective c++ 条款27,以下代码原文链接

class Animal { /* Some virtual members */ }
class Dog: public Animal {};
class Cat: public Animal {};

Dog     dog;
Cat     cat;
Animal& AnimalRef1 = dog;  // Notice no cast required. (Dogs and cats are animals).
Animal& AnimalRef2 = cat;
Animal* AnimalPtr1 = &dog;
Animal* AnimlaPtr2 = &cat;

Cat&    catRef1 = dynamic_cast<Cat&>(AnimalRef1);  // Throws an exception  AnimalRef1 is a dog
Cat*    catPtr1 = dynamic_cast<Cat*>(AnimalPtr1);  // Returns NULL         AnimalPtr1 is a dog
Cat&    catRef2 = dynamic_cast<Cat&>(AnimalRed2);  // Works
Cat*    catPtr2 = dynamic_cast<Cat*>(AnimalPtr2);  // Works

// This on the other hand makes no sense
// An animal object is not a cat. Therefore it can not be treated like a Cat.
Animal  a;
Cat&    catRef1 = dynamic_cast<Cat&>(a);    // Throws an exception  Its not a CAT
Cat*    catPtr1 = dynamic_cast<Cat*>(&a);   // Returns NULL         Its not a CAT.


大多数情况下,如果需要使用继承属性,则将数据定义为private,并且提供protected的接口,这样派生类可以定义自己的私有数据并且不会与基类的数据冲突,而且可以访问到基类的数据,更重要的是,在以后你需要对基类数据成员进行修改的时候不会使得派生类出错,这样可以最大化代码复用(只要不改变protected接口)。如果代码比较少,但是需要用到继承属性,可以定义少量protected数据,这样就是省去了定义一堆接口,简化代码。

要点:

在设计函数的时候Pass-by-value should be avoided for objects(传递const引用,而不是传值):

myMethod (const SomeClass &object)  // good
myMethod (SomeClass object)         // bad: don't do this


效率问题

如果参数是基类,则派生类会被截断。因为在传值的时候,基类的复制构造函数被调用(其参数为派生类对象),所以派生类的部分被切割掉了——effective c++ 第20条: 宁以 pass-by-reference-to-const 替换 pass-by-value。 原因: objects passed by value through a base-class variable will in effect behave as a base-class object without the extended information defined by the derived class,

虚析构函数:如果基类存在虚函数,则基类的析构函数必须是虚函数,防止内存泄露(slicing) 。

Interface: A class with no data and where all functions are pure virtual functions(一般为public) is often called an interface.

纯虚函数的实现 派生类必须重新定义纯虚函数,但是有些共性的代码可以在纯虚函数中的实现完成,例如,可以用来执行某些具有共性的缺省设置,在派生类中显式执行BaseClass::pure_virtual_func即可;缺省设置也可以通过在基类中定义protected的成员函数,然后派生来能够在其成员函数中调用基类的protected函数来完成缺省设置。

派生类调用基类函数

void Der::f()
{
  Base::f();  // Or, if you prefer, this->Base::f();
}


Virtual Constructor? 就是纯虚函数的 clone(复制) 和 create(构造),但是在派生来中返回的为派生类的指针,而不是基类指针。That’s how to define a copy constructor or assignment operator for a class that contains a pointer to a (abstract) base class:

class Shape {
public:
  virtual ~Shape() { }                 // A virtual destructor
  virtual void draw() = 0;             // A pure virtual function
  virtual void move() = 0;
  // ...
  virtual Shape* clone()  const = 0;   // Uses the copy constructor
  virtual Shape* create() const = 0;   // Uses the default constructor
};
class Circle : public Shape {
public:
  Circle* clone()  const;   // Covariant Return Types; see below
  Circle* create() const;   // Covariant Return Types; see below
  // ...
};
Circle* Circle::clone()  const { return new Circle(*this); }
Circle* Circle::create() const { return new Circle();      }


绝不重新定义继承而来的non-virtual函数:Never hide member functions that were public in my base class——effective c++条款36

class B{
public:
    void mf();
}
class D: public B{
public:
    void mf();  //遮掩了 B::mf;
};
D x;
B* pB = &x;
pB->mf();   // 调用 B::mf

D* pD = &x;
pD->mf();   // 调用 D::mf


如果不是通过指针调用,而是通过对象
x
调用,则只可能调用
D::mf
,即使基类的函数签名不同,而调用跟基类函数签名对应的函数版本(继承中的重载?)也会失败。因为这种遮掩只通过函数名来判断,即函数的名字(
mf
)。

这种名称查找规则:首先派生类作用域->基类作用域->最后在全局作用域;因此,除非派生类中没有
mf
函数,基类的
mf
函数才会被调用,基类中也没有则调用全局的
mf
函数——effective c++ 条款24,若所有参数皆需要类型转化,请为此采用non-member函数(即全局函数,或者在命名空间的函数)。

不要以多态方式处理数组,基类和派生类大小不同,数组中取地址不一。因为C++ can’t distinguish between a pointer-to-a-thing and a pointer-to-an-array-of-things——more effective c++ 条款3

class Base {
public:
  virtual void f(); 
};
class Derived : public Base {
public:
  // ...
private:
  int i_;
};
void userCode(Base* arrayOfBase)
{
  arrayOfBase[1].f(); // 此处如果传进来的是派生类,
                      //则编译器会按照基类的大小去在数组中找索引为1的指针,
                      //出现runtime error!
                      //如果使用std::array<Derived, 10> 能在编译的时候报错,而不是运行时出错。
}
int main()
{
  Derived arrayOfDerived[10];
  userCode(arrayOfDerived);
  // ...
}


在继承中使用重载? 基类需要使用重载函数时,一般是通过在基类中定义public的重载非虚函数调用protected的非重载虚函数,或者通过 using Base::func 来实现——effective c++ 条款33:

class Base {
public:
  void f(int x)    { f_int(x); }  // 重载非虚函数
  void f(double x) { f_dbl(x); }  // 重载非虚函数
protected:
  virtual void f_int(int);        // 非重载虚函数
  virtual void f_dbl(double);     // 非重载虚函数
};
//这么做是因为如果直接将重载函数定义为虚函数,
//则如果在派生类中重新定义重载函数中的某一个函数(例如在派生来中定义f(char x),
//则基类重载函数会被隐藏,and you are going to die!!!
//通过上面这样的修改,派生类直接重新定义f_int或者f_dbl就可以了。
//可以这么做,但是不推荐:
class Base {
public:
  void f(double x);
};
class Derived : public Base {
public:
  using Base::f;  // 通过这一句可以使用基类被隐藏的函数: Base::f(double x)
  void f(char c);
};
//通过在基类和派生来中定义名字相同,但是参数不同的函数是不能发生重载的,
//只会其中一个隐藏另一个(取决于调用的指针是基类还是派生类)。
//重载本应该定义在同一个类中。但是可以使用using Base::来将基类中的函数引入到派生类中,但是不推荐这么做。


to be continued….
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: