Effective C++: 尽量不要在构造函数中调用虚函数(virtual-function).

本文详细解析了C++中构造函数的执行流程,包括成员变量的初始化顺序、虚函数调用的行为以及派生类构造过程中的注意事项。通过具体示例展示了如何正确地构造类对象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 class object : public base{

public:

  object(type1 data1, type2, data2) //这里的 data1 和 data2就是我们下文中的: member initialization list

         :base(data1),  //这里的base(data1) 和 objdata(data2) 指的是 声明的初始化顺序.

         objdata2(data1),

          objdata(data2){}

 

private:

type2 objdata;  //这里的 指的是class内数据 成员的声明顺序.

type3 objdata2;

type 4 objdata3; //注意这里没有并没有放入到 声明的初始化顺序.

};

 

继承体系下的对象构造规则:

1, 记录在member initialization list中的data被按照constructor 声明的初始化顺序 放入,但是 是以class体内数据 成员(data members)的声明顺序 为顺序来进行初始化.

2, 如果一个data member没有出现在初始化列表中,就会调用它的默认构造函数(可能是合成的也可能是自定义的).

3,在 1和2 之前如果 class object 有 virtual table pointer(s), 它们必须被设定为初始值, 指向合适的virtual table(s).

4, 在 1, 2, 3 之前,所有上一层的 base class constructor(s)必须被调用,且:

   (1)如果base class constructor被列入当前 class object 声明的初始化列表中,那么任何显式指定的参数都应该被传递进去base class且规则符合1, 2.

   (2)如果base class constructor没有被列入当前 class object的初始化列表中,那么调用 base class的默认构造函数.

5, 在1, 2, 3, 4之前,所有的 virtual base class 必须被构造。

 

demo:

#include <iostream>

class Base {
public:
	Base(const int& data)
		:x{ data }
	{
		print();
	}

	Base() { std::cout << "Base default constructor!" << std::endl; }
	virtual ~Base() = default;

	Base(const Base& other) :x{ other.x } {}
	Base(Base&& other)
		:x{ other.x } {}

	Base& operator=(const Base& other)
	{
		this->x = other.x;
		return *this;
	}
	Base& operator=(Base&& other)
	{
		this->x = std::move(other.x);
		return *this;
	}

	virtual void print()
	{
		std::cout << "Base x: "<< this->x << std::endl;
	}

protected:
	int x;
};

class DerBase1 : virtual public Base{ 
public:
	DerBase1(const int& data)
		:Base{ data },
		y{ data }
	{
		print(); //调用了虚函数.
	}

	DerBase1() { std::cout << "DerBase1 default constructor!" << std::endl; }
	virtual ~DerBase1() = default;

	DerBase1(const DerBase1& other) :Base{ other }, y{ other.y } {}
	DerBase1(DerBase1&& other) 
		 :Base{ std::forward<DerBase1>(other) }, 
		  y{ other.y } {}

	DerBase1& operator=(const DerBase1& other)
	{
		Base::operator=(other);
		this->y = other.y;
		return *this;
	}

	DerBase1& operator=(DerBase1&& other)
	{
		Base::operator=(std::forward<DerBase1>(other));
		this->y = std::move(other.y);
		return *this;
	}

	virtual void print() override //注意这里的: override
	{
		std::cout << "DerBase1 y: " << this->y << std::endl;
	}

protected:
	int y;
};

class DerBase2 : virtual public Base {
public:
	DerBase2(const int& data)
		:Base{ data },
		z{ data }
	{
		print();
	}

	DerBase2() { std::cout << "DerBase2 default constructor!" << std::endl; }
	virtual ~DerBase2() = default;

	DerBase2(const DerBase2& other) 
		:Base{ other }, 
		 z{ other.z } {}

	DerBase2(DerBase2&& other) 
		:Base{ std::forward<DerBase2>(other) }, 
		 z{ other.z } {}

	DerBase2& operator=(const DerBase2& other)
	{
		Base::operator=(other);
		this->z = other.z;
		return *this;
	}

	DerBase2& operator=(DerBase2&& other)
	{
		Base::operator=(std::forward<DerBase2>(other));
		this->z = std::move(other.z);
		return *this;
	}

	virtual void print() override //注意override
	{
		std::cout << "DerBase2 y: " << this->z << std::endl;
	}

protected:
	int z;
};

class DDer : public DerBase1, public DerBase2 {
public:
	DDer() { std::cout << "DDer default constructor!" << std::endl; }
	virtual ~DDer() = default;

	DDer(const int& data_)
		:DerBase1{ data_ },
		DerBase2{ data_ },
		data{ data_ }
	{
		print();
	}

	DDer(const DDer& other) 
		:DerBase1{ other }, 
		 DerBase2{ other } {}

	DDer(DDer&& other) 
		:DerBase1{ std::forward<DDer>(other) }, 
		 DerBase2{ std::forward<DerBase2>(other) }, 
		 data(std::move(other.data)){}

	DDer& operator=(const DDer& other)
	{
		DerBase1::operator=(other);
		DerBase2::operator=(other);
		this->data = other.data;
		return *this;
	}

	DDer& operator=(DDer&& other)
	{
		DerBase1::operator=(std::forward<DDer>(other));
		DerBase2::operator=(std::forward<DDer>(other));
		this->data = std::move(other.data);
		return *this;
	}


	virtual void print() override
	{
		std::cout << "DDer data: " << this->data << std::endl;
	}

private:
	int data;
};

int main()
{
	DerBase1 der1{20};
	Base& base = der1; 

	return 0;
}

通过以上demo我们可以看出来在派生类构造的过程中调用虚函数并不会产生被动态调用! 而且也很容易被人误解. 因此我们在构造函数中调用虚函数的时候最好指明:

比如 DerBase1::print(); 这样进行调用.

 

也绝对不要在派生类(derived-class)通过static_cast<Base>(*this)把当前派生类转换成基类对象调用基类中的虚函数.虽然能过通过编译,但是这些操作实际上面是在*this的副本上面进行的因此当前this并不会受到影响。

 #include <iostream>
class Base{
 private:
 int number_;
 
 public:
 Base()=default;
 
 Base(const int& n_);
 
 virtual void change_data();
 
 void print()const;
 
 void change()noexcept;
 
 virtual ~Base()=default; 
};
Base::Base(const int& n_)
     :number_(n_)
{
 //
}
void Base::change_data()
{
 std::cout<<"change number in Base. "<<std::endl;
 this->number_ = 999;
}
void Base::print()const
{
 std::cout<<"print:  "<<this->number_<<std::endl;
}
void Base::change()noexcept
{
 std::cout<<"Reset number in Base. "<<std::endl;
 this->number_ = 888;
}
class Derived : public Base{
 private:
  std::string str_;
  
  public:
   Derived()=default;
   
   Derived(const std::string& parameter_);
   
   virtual void change_data()override;
   
   ~Derived()=default;
};
Derived::Derived(const std::string& parameter_)
        :Base(100),
         str_(parameter_)
{
 //
}
void Derived::change_data()
{
 static_cast<Base>(*this).change(); //这里使用了类型转换,然后调用被转换后对象的成员函数。
 //虽然*this被转换成了Base, 但是Base的操作实际上是在 *this 的副本上执行的.因此并不会改变当前Derived内的数据内容. 
 
 static_cast<Base>(*this).change_data(); //同上. 
 
 std::cout<<this->str_<<std::endl;
}
int main()
{
 Base first_(0);
 Derived second_("shihua");
 
 second_.change_data();
 second_.print(); //Base::print(); number = 100. 
 
 
 return 0;
}

转载于:https://my.oschina.net/SHIHUAMarryMe/blog/645914

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值