`
897371388
  • 浏览: 526618 次
文章分类
社区版块
存档分类
最新评论

一个VC编译错误引发的对显示类型转换的思考(static_cast、dynamic_cast和const_cast)

 
阅读更多

一、问题提出

今天在研究effective c++中碰到的copy构造函数的时候,运行了下面这个程序,编译出现了错误:

#include<iostream>

using namespace std;

class point 
{ 
private: 
	int m_x,m_y; 
public: 
	point() 
	{ 
		m_x = 0; 
		m_y = 0; 
	} 
	point(int x, int y) 
	{ 
		m_x = x; 
		m_y = y; 
	} 
	point(const point& p)
	{
		m_x = p.m_x;
		m_y = p.m_y;
		cout<<"copy constructor is called!"<<endl;
	}
	
	static point reverse(const point& p)
	{
		point p1;
		p1.m_x = p.getY();
		p1.m_y = p.getX();

		return p1;
	}
	int getX()
	{ 
		return m_x; 
	} 
	int getY()
	{ 
		return m_y; 
	}
	void print()
	{
		cout<<m_x<<" "<<m_y<<endl;
	}
}; 

int main()
{
	point p(1, 2);	
	point p1(p);  //initialize p1 with p
	point p2 = point::reverse(p1);	
	p2.print();
	return 0;
}

错误信息为:

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Compiling...
item3.cpp
D:\Project\EffctiveCProj1\item3.cpp(32) : error C2662: 'getY' : cannot convert 'this' pointer from 'const class point' to 'class point &'
Conversion loses qualifiers
D:\Project\EffctiveCProj1\item3.cpp(33) : error C2662: 'getX' : cannot convert 'this' pointer from 'const class point' to 'class point &'
Conversion loses qualifiers
Error executing cl.exe.

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

二、问题分析


分析了一下错误提示,发现是不能将类型从const class point转为class point &,定位到代码,原来出现在line21和line22 getY()和getX()调用这里:

p1.m_x = p.getY();
p1.m_y = p.getX();

p是一个const point&类型,而getY()和getX()都是非const类型,按照C++的规定,const常量无法调用非const函数,只可以调用const函数,因为const常量对象的数据成员是不允许改变的,非const函数就有可能改变数据成员,因此不允许调用!


三、问题解决


既然明白了这点,就很好修改了,两种方式:

A、将getX()和getY()改成const函数(简单)

const函数只需要在函数定义的地方的参数列表之后花括号之前加上const即可

B、将p转换为非const变量(稍微复杂)

在《effective c++》的“item 03:尽可能的使用const”一章中,作者介绍了如何将const类型转换为非const类型,即:采用static_cast或者const_cast.

按照plan A的修改:

#include<iostream>

using namespace std;

class point 
{ 
private: 
	int m_x,m_y; 
public: 
	point() 
	{ 
		m_x = 0; 
		m_y = 0; 
	} 
	point(int x, int y) 
	{ 
		m_x = x; 
		m_y = y; 
	} 
	point(const point& p)
	{
		m_x = p.m_x;
		m_y = p.m_y;
		cout<<"copy constructor is called!"<<endl;
	}
	
	static point reverse(const point& p)
	{
		point p1;
		p1.m_x = p.getY();
		p1.m_y = p.getX();

		return p1;
	}
	int getX() const  //const函数
	{ 
		return m_x; 
	} 
	int getY() const //const函数
	{ 
		return m_y; 
	}
	void print()
	{
		cout<<m_x<<" "<<m_y<<endl;
	}
}; 

int main()
{
	point p(1, 2);	
	point p1(p);  //initialize p1 with p
	point p2 = point::reverse(p1);	
	p2.print();
	return 0;
}

bingo,编译通过!

按照plan B的修改:

#include<iostream>

using namespace std;

class point 
{ 
private: 
	int m_x,m_y; 
public: 
	point() 
	{ 
		m_x = 0; 
		m_y = 0; 
	} 
	point(int x, int y) 
	{ 
		m_x = x; 
		m_y = y; 
	} 
	point(const point& p)
	{
		m_x = p.m_x;
		m_y = p.m_y;
		cout<<"copy constructor is called!"<<endl;
	}
	
	static point reverse(const point& p)
	{
		point p1;
		p1.m_x = static_cast<point>(p).getY();  //将const point类型显示转换为point类型
		p1.m_y = static_cast<point>(p).getX();  //将const point类型显示转换为point类型
		return p1;
	}
	int getX()
	{ 
		return m_x; 
	} 
	int getY()
	{ 
		return m_y; 
	}
	void print()
	{
		cout<<m_x<<" "<<m_y<<endl;
	}
}; 

int main()
{
	point p(1, 2);	
	point p1(p);  //initialize p1 with p
	point p2 = point::reverse(p1);	
	p2.print();
	return 0;
}

同样编译通过了!

对于plan B,同样可以采用const_cast进行类型转换:

// CastExample.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include<iostream>

using namespace std;

class point 
{ 
private: 
	int m_x,m_y; 
public: 
	point() 
	{ 
		m_x = 0; 
		m_y = 0; 
	} 
	point(int x, int y) 
	{ 
		m_x = x; 
		m_y = y; 
	} 
	point(const point& p)
	{
		m_x = p.m_x;
		m_y = p.m_y;
		cout<<"copy constructor is called!"<<endl;
	}

	static point reverse(const point& p)
	{
		point p1;
		p1.m_x = const_cast<point&>(p).getY();  //将const point类型显示转换为point类型
		p1.m_y = const_cast<point&>(p).getX();  //将const point类型显示转换为point类型
		return p1;
	}
	int getX()
	{ 
		return m_x; 
	} 
	int getY()
	{ 
		return m_y; 
	}
	void print()
	{
		cout<<m_x<<" "<<m_y<<endl;
	}
}; 

int main()
{
	point p(1, 2);	
	point p1(p);  //initialize p1 with p
	point p2 = point::reverse(p1);	
	p2.print();
	return 0;
}

注意:在使用const_cast进行转换的时候,尖括号中的参数必须是引用类型,即:const_cast<point&>(p).getY()


四、进一步探讨(关于C++类型转换)


C++类型转换分为:隐式类型转换和显式类型转换

1.隐式类型转换


又称为“标准转换”或者“自动类型转换”,由编译系统自动完成,对程序员透明。包括以下几种情况:

1) 算术转换(Arithmetic conversion) : 在混合类型的算术表达式中, 最宽的数据类型成为目标转换类型。

int ival = 3;
double dval = 3.14159;
ival + dval;//ival被提升为double类型,结果是个double类型

2)一种类型表达式赋值给另一种类型的对象:目标类型是被赋值对象的类型

int *pi = 0; // 0被转化为int *类型
ival = dval; // double->int

例外:void指针赋值给其他指定类型指针时,不存在标准转换,编译出错

3)将一个表达式作为实参传递给函数调用,此时形参和实参类型不一致:目标转换类型为形参的类型

extern double sqrt(double);
cout << "The square root of 2 is " << sqrt(2) << endl;//2被提升为double类型:2.0

4)从一个函数返回一个表达式,表达式类型与返回类型不一致:目标转换类型为函数的返回类型

double difference(int ival1, int ival2)
{
return ival1 - ival2; //返回值被提升为double类型
}

2.显式类型转换


又称“强制类型转换”(cast),由程序员完成。
C++标准定义了四个类型转换符:reinterpret_cast, static_cast, dynamic_cast 和 const_cast,目的在于控制类(class)之间的类型转换。

(1)static_cast


用法:static_cast <type-id> ( expression )
说明:该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。做这些转换前,你必须确定要转换的数据确实是目标类型的数据,因为static_cast不做运行时的类型检查以保证转换的安全性。也因此,static_cast不如dynamic_cast安全。对含有二义性的指针,dynamic_cast会转换失败,而static_cast却直接且粗暴地进行转换。这是非常危险的。
使用场景
a、用于类层次结构中基类和子类之间指针或引用的转换。
进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。
b、用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
c、把void指针转换成目标类型的指针(不安全!!)
d、把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。
举例
a、用于类层次结构中基类和子类之间指针或引用的转换。
// CastExample.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include<iostream>

using namespace std;

class Point 
{ 
protected: 
	int m_x,m_y; 
public: 
	Point() 
	{ 
		m_x = 0; 
		m_y = 0; 
	} 
	Point(int x, int y) 
	{ 
		m_x = x; 
		m_y = y; 
	} 
	void print()
	{
		cout<<m_x<<" "<<m_y<<endl;
	}
}; 

class Point3d : public Point
{
private:
	int m_z;
public:
	Point3d(int x, int y, int z):Point(x, y),m_z(z){}
	void print()
	{
		cout<<m_x<<" "<<m_y<<" "<<m_z<<endl;
	}
};

void f(Point* p1, Point3d* p2)
{
	Point* pa = static_cast<Point*>(p2);    //safe
	Point3d* pb = static_cast<Point3d*>(p1);//unsafe

	pa->print();
	pb->print();
}

int main()
{
	Point p(1, 2);
	Point3d p3d(1, 2, 3);
	f(&p, &p3d);

	return 0;
}

运行结果为:


虽然编译能够通过,但是运行出的结果很明显是不正确的!
Point* pa = static_cast<Point*>(p2); 是将子类类型对象转为父类类型对象,因为父类对象的数据成员子类都可以提供,所以没有问题。
Point3d* pb = static_cast<Point3d*>(p1); 是将父类类型对象转为子类类型对象,因为子类对象的数据成员包括了父类没有的数据成员,所以会造成子类某些数据成员无法赋值,出现“随机值”,出现了问题。

b、在两个类对象之间进行转换,比如把类型为A的对象a,转换为类型为B的对象。如下:
#include "stdafx.h"
#include<iostream>
#include<string>

using namespace std;

class Student 
{ 
public:
	string name;
	int age;
	string sno;
public:
	Student(string name = "", int age = 20, string sno = ""):name(name),age(age),sno(sno){}
	void print()
	{
		cout<<"name = "<<name<<" age = "<<age<<" sno = "<<sno<<endl;
	}
};

class Teacher
{
private:
	string name;
	int age;
	string tno;
public:
	Teacher(string name = "", int age = 20, string tno = ""):name(name),age(age),tno(tno){}
	Teacher(const Student& stu)
	{
		name = stu.name;
		age = stu.age;
		tno = stu.sno;
	}
	void print()
	{
		cout<<"name = "<<name<<" age = "<<age<<" tno = "<<tno<<endl;
	}
};

int main()
{
	Student stu("wanjun", 24, "12345");
	Teacher tea = static_cast<Teacher>(stu);
	tea.print();
}

如果让以上代码通过编译,那么Teacher类必须含有以Student类的对象(或对象的引用)为参数的构造函数。即:

	Teacher(const Student& stu)
	{
		name = stu.name;
		age = stu.age;
		tno = stu.sno;
	}
这实际上是把转换的工作交给构造函数去做了
这是一个很重要的发现,我们来验证一下:
将例a中的代码修改一下,去掉所有的指针,直接使用对象
// CastExample.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include<iostream>

using namespace std;

class Point 
{ 
public: 
	int m_x,m_y; 
public: 
	Point() 
	{ 
		m_x = 0; 
		m_y = 0; 
	} 
	Point(int x, int y) 
	{ 
		m_x = x; 
		m_y = y; 
	} 
	void print()
	{
		cout<<m_x<<" "<<m_y<<endl;
	}
}; 

class Point3d : public Point
{
private:
	int m_z;
public:
	Point3d(int x, int y, int z):Point(x, y),m_z(z){}
	void print()
	{
		cout<<m_x<<" "<<m_y<<" "<<m_z<<endl;
	}
};

void f(Point p1, Point3d p2)
{
	Point pa = static_cast<Point>(p2);    
	Point3d pb = static_cast<Point3d>(p1); // 出现error

	pa.print();
	pb.print();
}

int main()
{
	Point p1(1, 2);
	Point3d p2(1, 2, 3);

	f(p1, p2);
	return 0;
}
不好意思,语法检查出错了:
Point3d pb = static_cast<Point3d>(p1); 这一行显示:error:不存在用户定义的从Point到Point3d的转换。意思很明显,缺少转换函数,也就是copy构造函数,我们给他加上:
// CastExample.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include<iostream>

using namespace std;

class Point 
{ 
public: 
	int m_x,m_y; 
public: 
	Point() 
	{ 
		m_x = 0; 
		m_y = 0; 
	} 
	Point(int x, int y) 
	{ 
		m_x = x; 
		m_y = y; 
	} 
	void print()
	{
		cout<<m_x<<" "<<m_y<<endl;
	}
}; 

class Point3d : public Point
{
private:
	int m_z;
public:
	Point3d(int x, int y, int z):Point(x, y),m_z(z){}

	Point3d(const Point& p) //copy 构造函数
	{
		m_x = p.m_x;
		m_y = p.m_y;
		m_z = p.m_x + p.m_y;
	}

	void print()
	{
		cout<<m_x<<" "<<m_y<<" "<<m_z<<endl;
	}
};

void f(Point p1, Point3d p2)
{
	Point pa = static_cast<Point>(p2);    
	Point3d pb = static_cast<Point3d>(p1);

	pa.print();  // 输出1、2
	pb.print();  // 输出1、2、3
}

int main()
{
	Point p1(1, 2);
	Point3d p2(1, 2, 3);

	f(p1, p2);
	return 0;
}
运行成功,而且结果没有出现例a中的“随机值”!
这充分证明了static_cast对类类型的转换是借助copy构造函数进行的,所以在进行转换的时候需要考虑:如果类默认的copy构造函数不能实现需要的功能,需要自定义copy构造函数,这样才可以避免unsafe的情况!

(2)dynamic_cast

用法:dynamic_cast <type-id> (expression)
说明:该运算符把expression转换成type-id类型的对象。
a、dynamic_cast只用于对象的指针和引用。如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。
b、检测在运行时进行。如果被转换的指针不是一个被请求的有效完整的对象指针,返回值为NULL.
使用场景
a、dynamic_cast主要用于类层次间的上行转换和下行转换
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
b用于类之间的交叉转换。

把上面的例a改为采用dynamic_cast 的方式,并做适当修改:

// CastExample.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include<iostream>

using namespace std;

class Point 
{ 
public: 
	int m_x,m_y; 
public: 
	virtual void foo(){};
	Point() 
	{ 
		m_x = 0; 
		m_y = 0; 
	} 
	Point(int x, int y) 
	{ 
		m_x = x; 
		m_y = y; 
	} 
	void print()
	{
		cout<<m_x<<" "<<m_y<<endl;
	}
}; 

class Point3d : public Point
{
private:
	int m_z;
public:
	Point3d(int x, int y, int z):Point(x, y),m_z(z){}

	void print()
	{
		cout<<m_x<<" "<<m_y<<" "<<m_z<<endl;
	}
	void foo()
	{

	}
};

void f(Point *p1, Point3d *p2)
{
	Point *pa = dynamic_cast<Point*>(p2);    
	Point3d *pb = dynamic_cast<Point3d*>(p1); // pb is null

	if (pa)
	{
		pa->print();
	}
	if (pb)
	{
		pb->print();
	}
}

int main()
{
	Point p1(1, 2);
	Point3d p2(1, 2, 3);

	f(&p1, &p2);
	return 0;
}
在上面的代码段中,将子类对象p2转为父类对象pa是安全的,而将父类对象p1转为子类对象pb也是安全的,因为pb将是一个空指针(即0,因为dynamic_cast失败)。
另外要注意:Base要有虚函数,否则会编译出错;static_cast则没有这个限制

这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。

dynamic_cast支持交叉转换(cross cast)如下代码所示:

class Base
{
public:
    int m_iNum;
    virtual void f(){}
};



class Derived1 : public Base
{

};

class Derived2 : public Base
{

};

void foo()
{
    derived1 *pd1 = new Drived1;

    pd1->m_iNum = 100;

    Derived2 *pd2 = static_cast<Derived2 *>(pd1); //compile error

    Derived2 *pd2 = dynamic_cast<Derived2 *>(pd1); //pd2 is NULL

    delete pd1;
}

在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。

(3)const_cast


用法:const_cast<type_id>(expression)
说明:该运算符用来修改类型的const或volatile属性。除了const或volatile修饰之外,type_id和expression的类型是一样的。
使用场景
a、常量指针被转化成非常量指针,并且仍然指向原来的对象;
b、常量引用被转换成非常量引用,并且仍然指向原来的对象;
c、常量对象被转换成非常量对象。
Voiatile和const类似。

// CastExample.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include<iostream>

using namespace std;

class point 
{ 
private: 
	int m_x,m_y; 
public: 
	point() 
	{ 
		m_x = 0; 
		m_y = 0; 
	} 
	point(int x, int y) 
	{ 
		m_x = x; 
		m_y = y; 
	} 
	point(const point& p)
	{
		m_x = p.m_x;
		m_y = p.m_y;
		cout<<"copy constructor is called!"<<endl;
	}
	
	static point reverse(const point& p)
	{
		point p1, p2;
		p2 = const_cast<point&>(p); //将const point类型显示转换为point类型
		p1.m_x = p2.getY(); 
		p1.m_y = p2.getX();  
		return p1;
	}
	int getX()
	{ 
		return m_x; 
	} 
	int getY()
	{ 
		return m_y; 
	}
	void print()
	{
		cout<<m_x<<" "<<m_y<<endl;
	}
}; 

int main()
{
	point p(1, 2);	
	point p1(p);  //initialize p1 with p
	point p2 = point::reverse(p1);	
	p2.print();
	return 0;
}

注意<>和()中的类型一定要一致,都为引用或者都为指针或都为对象!


五、小结


C++的四种强制转型形式每一种适用于特定的目的:


  ·dynamic_cast 主要用于执行“安全的向下转型(safe downcasting)”,也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。它是唯一不能用旧风格语法执行的强制转型,也是唯一可能有重大运行时代价的强制转型。

·static_cast 可以被用于强制隐型转换(例如,non-const 对象转型为 const 对象,int 转型为 double,等等),它还可以用于很多这样的转换的反向转换(例如,void* 指针转型为有类型指针,基类指针转型为派生类指针),但是它不能将一个 const 对象转型为 non-const 对象(只有 const_cast 能做到),它最接近于C-style的转换。

  ·const_cast 一般用于强制消除对象的常量性。它是唯一能做到这一点的 C++ 风格的强制转型。

  ·reinterpret_cast 是特意用于底层的强制转型,导致实现依赖(implementation-dependent)(就是说,不可移植)的结果,例如,将一个指针转型为一个整数。这样的强制转型在底层代码以外应该极为罕见。


(2013年11月14日 0:02)

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics