天天看点

C++组合和继承 组合中会涉及到默认构造函数和拷贝构造函数的问题背景

背景

   上午师弟让我看一下他遇到的问题,让我意识到组合问题理解的还不是很清晰,顺便把他的代码记录下来。

       在speed类中需要使用wheel的一个对象,需要对wheel初始化,在speed的构造函数里使用了赋值初始化,所以在wheel中需要定义一个默认构造函数。第一眼看下边的代码觉得没问题,编译老是链接器错误在speed构造函数里,之后改为对象指针解决。后边慢慢看了一下发现问题:

      针对这一问题有几种解决方法:

       1.如注释所示将声明写为定义。

       2.成员变量为类类型对象变为类类型指针。在构造函数中指向传过来的引用对象。     

       3.赋值初始化更改为列表初始化。赋值初始化会调用默认构造函数,列表初始化调用拷贝构造函数。

        关于他们之间的区别推荐几个blog:

        http://blog.csdn.net/u011941546/article/details/52925787

       http://blog.csdn.net/xiaofei2010/article/details/7974240

class wheel{
public:
	wheel();//这个是声明啊,定义呢?因为在传值的时候会构造一个临时对象而调用默认构造函数。更改为wheel(){};
        wheel(int a, int b)
	{
		a = a;
		b = b;
	}
	int a, b;
};

class speed{

public:
	speed(wheel temp)
	{
		whl = temp;
	}
	wheel whl;
};

int main()
{
	wheel first(1, 2);
	speed sp(first);
	return 0;
}
           

拷贝构造

拷贝构造函数被调用的几种情况以下引自《C++Peimer》5th p441:

      1.将一个对象作为实参传递给一个非引用类的形参。

      2.从一个返回类型为非引用类型的函数返回一个对象。

      3.用花括号列表初始化一个数组中元素或者一个聚合类的成员。

注意:当使用组合,类的成员变量是类类型。需要在构造函数中初始化,执行顺序是:

C++组合和继承 组合中会涉及到默认构造函数和拷贝构造函数的问题背景

可以看到,尽管传过来的是对象引用,但是还是调用了构造函数。这是为什么呢?说好的引用不产生临时对象呢?

这里并不是说结论不正确,而是使用的地方不对。引用不产生临时对象是在函数中当做形参而言的。

这里我们应该从类的成员变量初始化角度来考虑。

初始化列表和赋值

以下引自《C++Peimer》5th p258:

1.就对象的数据成员而言,初始化和赋值是有区别的。如果没有在构造函数的初始值列表中显示地初始化成员,则该成员将在构造函数体之前执行默认构造函数。

  也就是说尽管构造函数中的形参是引用,但是没有进行初始化列表,那么在进入函数体之前,会调用默认构造函数进行初始化。

2.初始化和赋值的区别事关底层效率的问题:前者直接初始化数据成员,后者则先初始化在赋值。

3.如果成员变量是const,引用,或者属于某种未提供默认构造函数的类类型,必须通过构造函数初始化列表为这些成员提供初始值。

FuncInit(Base &c) //在这里又调用了一次Base构造函数???
	{
		mBase = c;
		cout << "class FuncInit: constructor is called." << endl;
	}
           

将函数引用参数去掉后多了个拷贝构造函数是因为拷贝构造的第1点,同样不是列表初始化需要调用默认构造函数初始化。

  1.将一个对象作为实参传递给一个非引用类的形参。

C++组合和继承 组合中会涉及到默认构造函数和拷贝构造函数的问题背景
class Base
{
public:
	Base()
	{
		mData = 1;
		cout << "class Base: constructor is called." << endl;
	}

	Base(const Base& c)
	{
		mData = c.mData;
		cout << "class Base: copy constructor is called." << endl;
	}

	Base& operator =(Base& c)
	{
		this->mData = c.mData;
		cout << "class Base: assignment operation." << endl;
		return *this;
	}

private:
	int mData;
};

class ListInit
{
public:
	ListInit(Base c) : mBase(c)
	{

	}

private:
	Base mBase;
};

class FuncInit
{
public:
	FuncInit(Base &c) //在这里又调用了一次Base构造函数???
	{
		mBase = c;
		cout << "class FuncInit: constructor is called." << endl;
	}

private:
	Base mBase;
};

int main()
{
	Base b;    //调用一次Base构造函数
	FuncInit f(b);
	cout << endl;
        return 0;
}
           

一、组合和继承的关系

  类继承和对象组合是复用的两种最常用的技术。

一:继承

  继承是Is a 的关系,比如说Student继承Person,则说明Student is a Person。继承的优点是子类可以重写父类的方法来方便地实现对父类的扩展。

  继承的缺点有以下几点:

  ①:父类的内部细节对子类是可见的。

  ②:子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。

  ③:如果对父类的方法做了修改的话(比如增加了一个参数),则子类的方法必须做出相应的修改。所以说子类与父类是一种高耦合,违背了面向对象思想。

二:组合

  组合也就是设计类的时候把要组合的类的对象加入到该类中作为自己的成员变量。

  组合的优点:

  ①:当前对象只能通过所包含的那个对象去调用其方法,所以所包含的对象的内部细节对当前对象时不可见的。

  ②:当前对象与包含的对象是一个低耦合关系,如果修改包含对象的类中代码不需要修改当前对象类的代码。

  ③:当前对象可以在运行时动态的绑定所包含的对象。可以通过set方法给所包含对象赋值。

  组合的缺点:①:容易产生过多的对象。②:为了能组合多个对象,必须仔细对接口进行定义。

由此可见,组合比继承更具灵活性和稳定性,所以在设计的时候优先使用组合。只有当下列条件满足时才考虑使用继承:

  • 子类是一种特殊的类型,而不只是父类的一个角色
  • 子类的实例不需要变成另一个类的对象
  • 子类扩展,而不是覆盖或者使父类的功能失效

   以上摘录自:http://www.cnblogs.com/liuling/archive/2013/05/01/extends.html

二、组合和继承的例子

C++的“继承”特性可以提高程序的可复用性。正因为“继承”太有用、太容易用,才要防止乱用“继承”。我们要给“继承”立一些使用规则:

  一、如果类A 和类B 毫不相关,不可以为了使B 的功能更多些而让B 继承A 的功能。

  不要觉得“不吃白不吃”,让一个好端端的健壮青年无缘无故地吃人参补身体。

  二、如果类B 有必要使用A 的功能,则要分两种情况考虑:

(1)若在逻辑上B 是A 的“一种”(a kind of ),则允许B 继承A 的功能。如男人(Man)是人(Human)的一种,男孩(Boy)是男人的一种。那么类Man 可以从类Human 派生,类Boy 可以从类Man 派生。示例程序如下:

class Human
{
  …
};
class Man : public Human
{
  …
};
class Boy : public Man
{
  …
};
           

(2)若在逻辑上A 是B 的“一部分”(a part of),则不允许B 继承A 的功能,而是要用A和其它东西组合出B。例如眼(Eye)、鼻(Nose)、口(Mouth)、耳(Ear)是头(Head)的一部分,所以类Head 应该由类Eye、Nose、Mouth、Ear 组合而成,不是派生而成。示例程序如下:

class Eye
{
public:
  void Look(void);
};
class Nose
{
public:
  void Smell(void);
};
class Mouth
{
public:
  void Eat(void);
};
class Ear
{
public:
  void Listen(void);
};
// 正确的设计,冗长的程序
class Head
{
public:
  void Look(void) { m_eye.Look(); }
  void Smell(void) { m_nose.Smell(); }
  void Eat(void) { m_mouth.Eat(); }
  void Listen(void) { m_ear.Listen(); }
private:
  Eye m_eye;
  Nose m_nose;
  Mouth m_mouth;
Ear m_ear;
};
           

如果允许Head 从Eye、Nose、Mouth、Ear 派生而成,那么Head 将自动具有Look、Smell、Eat、Listen 这些功能:

// 错误的设计
class Head : public Eye, public Nose, public Mouth, public Ear
{
};
           

上述程序十分简短并且运行正确,但是这种设计却是错误的。这就开头所说的很多程序员经不起“继承”的诱惑而犯下设计错误。

一只公鸡使劲地追打一只刚下了蛋的母鸡,你知道为什么吗?

因为母鸡下了鸭蛋。

以上摘录自:http://www.cnblogs.com/BeyondAnyTime/archive/2012/05/20/2510770.html

该博主对C++的一些知识点总结的很好,不妨移步去看看。

继续阅读