一、问题提出
今天在研究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++类型转换)
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。这种转换的安全性也要开发人员来保证。
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;
}
运行结果为:
虽然编译能够通过,但是运行出的结果很明显是不正确的!
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;
}
这实际上是把转换的工作交给构造函数去做了。
// 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;
}
// 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中的“随机值”!
(2)dynamic_cast
说明:该运算符把expression转换成type-id类型的对象。
使用场景:
a、dynamic_cast主要用于类层次间的上行转换和下行转换
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
把上面的例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)