什么时候该使用前置声明?
2015-08-28 15:27
232 查看
首先,我们为什么要包括头文件?问题的回答很简单,通常是我们需要获得某个类型的定义(definition)。那么接下来的问题就是,在什么情 况下我们才需要类型的定义,在什么情况下我们只需要声明就足够了?问题的回答是当我们需要知道这个类型的大小或者需要知道它的函数签名的时候,我们就需要获得它的定义。
假设我们有类型A和类型C,在哪些情况下在A需要C的定义:
A继承至C
A有一个类型为C的成员变量
A有一个类型为C的指针的成员变量
A有一个类型为C的引用的成员变量
A有一个类型为std::list<C>的成员变量
A有一个函数,它的签名中参数和返回值都是类型C
A有一个函数,它的签名中参数和返回值都是类型C,它调用了C的某个函数,代码在头文件中
A有一个函数,它的签名中参数和返回值都是类型C(包括类型C本身,C的引用类型和C的指针类型),并且它会调用另外一个使用C的函数,代码直接写在A的头文件中
C和A在同一个名字空间里面
C和A在不同的名字空间里面
1,没有任何办法,必须要获得C的定义,因为我们必须要知道C的成员变量,成员函数。
2,需要C的定义,因为我们要知道C的大小来确定A的大小,但是可以使用Pimpl惯用法来改善这一点,详情请看Hurb的Exceptional C++。
3,4,不需要,前置声明就可以了,其实3和4是一样的,引用在物理上也是一个指针,它的大小根据平台不同,可能是32位也可能是64位,反正我们不需要知道C的定义就可以确定这个成员变量的大小。
5,不需要,有可能老式的编译器需要。标准库里面的容器像list, vector,map,在包括一个list<C>,vector<C>,map<C, C>类型的成员变量的时候,都不需要C的定义。因为它们内部其实也是使用C的指针作为成员变量,它们的大小一开始就是固定的了,不会根据模版参数的不同而改变。
6,不需要,只要我们没有使用到C。
7,需要,我们需要知道调用函数的签名。
8,8的情况比较复杂,直接看代码会比较清楚一些。
C& doToC(C&);
C& doToC2(C& c) {return doToC(c);};
从上面的代码来看,A的一个成员函数doToC2调用了另外一个成员函数doToC,但是无论是doToC2,还是doToC,它们的的参数和返 回类型其实都是C的引用(换成指针,情况也一样),引用的赋值跟指针的赋值都是一样,无非就是整形的赋值,所以这里即不需要知道C的大小也没有调用C的任 何函数,实际上这里并不需要C的定义。
但是,我们随便把其中一个C&换成C,比如像下面的几种示例:
1.
C& doToC(C&);
C& doToC2(C c) {return doToC(c);};
2.
C& doToC(C);
C& doToC2(C& c) {return doToC(c);};
3.
C doToC(C&);
C& doToC2(C& c) {return doToC(c);};
4.
C& doToC(C&);
C doToC2(C& c) {return doToC(c);};
无论哪一种,其实都隐式包含了一个拷贝构造函数的调用,比如1中参数c由拷贝构造函数生成,3中doToC的返回值是一个由拷贝构造函数生成的匿名对象。因为我们调用了C的拷贝构造函数,所以以上无论那种情形都需要知道C的定义。
9和10都一样,我们都不需要知道C的定义,只是10的情况下,前置声明的语法会稍微复杂一些。
最后给出一个完整的例子,我们可以看到在两个不同名字空间的类型A和C,A是如何使用前置声明来取代直接包括C的头文件的:
A.h
#pragma once
#include <list>
#include <vector>
#include <map>
#include <utility>
//不同名字空间的前置声明方式
namespace test1
{
class C;
}
namespace test2
{
//用using避免使用完全限定名
using test1::C;
class A
{
public:
C useC(C);
C& doToC(C&);
C& doToC2(C& c) {return doToC(c);};
private:
std::list<C> _list;
std::vector<C> _vector;
std::map<C, C> _map;
C* _pc;
C& _rc;
};
}
C.h
#ifndef C_H
#define C_H
#include <iostream>
namespace test1
{
class C
{
public:
void print() {std::cout<<"Class C"<<std::endl;}
};
}
#endif // C_H
假设我们有类型A和类型C,在哪些情况下在A需要C的定义:
A继承至C
A有一个类型为C的成员变量
A有一个类型为C的指针的成员变量
A有一个类型为C的引用的成员变量
A有一个类型为std::list<C>的成员变量
A有一个函数,它的签名中参数和返回值都是类型C
A有一个函数,它的签名中参数和返回值都是类型C,它调用了C的某个函数,代码在头文件中
A有一个函数,它的签名中参数和返回值都是类型C(包括类型C本身,C的引用类型和C的指针类型),并且它会调用另外一个使用C的函数,代码直接写在A的头文件中
C和A在同一个名字空间里面
C和A在不同的名字空间里面
1,没有任何办法,必须要获得C的定义,因为我们必须要知道C的成员变量,成员函数。
2,需要C的定义,因为我们要知道C的大小来确定A的大小,但是可以使用Pimpl惯用法来改善这一点,详情请看Hurb的Exceptional C++。
3,4,不需要,前置声明就可以了,其实3和4是一样的,引用在物理上也是一个指针,它的大小根据平台不同,可能是32位也可能是64位,反正我们不需要知道C的定义就可以确定这个成员变量的大小。
5,不需要,有可能老式的编译器需要。标准库里面的容器像list, vector,map,在包括一个list<C>,vector<C>,map<C, C>类型的成员变量的时候,都不需要C的定义。因为它们内部其实也是使用C的指针作为成员变量,它们的大小一开始就是固定的了,不会根据模版参数的不同而改变。
6,不需要,只要我们没有使用到C。
7,需要,我们需要知道调用函数的签名。
8,8的情况比较复杂,直接看代码会比较清楚一些。
C& doToC(C&);
C& doToC2(C& c) {return doToC(c);};
从上面的代码来看,A的一个成员函数doToC2调用了另外一个成员函数doToC,但是无论是doToC2,还是doToC,它们的的参数和返 回类型其实都是C的引用(换成指针,情况也一样),引用的赋值跟指针的赋值都是一样,无非就是整形的赋值,所以这里即不需要知道C的大小也没有调用C的任 何函数,实际上这里并不需要C的定义。
但是,我们随便把其中一个C&换成C,比如像下面的几种示例:
1.
C& doToC(C&);
C& doToC2(C c) {return doToC(c);};
2.
C& doToC(C);
C& doToC2(C& c) {return doToC(c);};
3.
C doToC(C&);
C& doToC2(C& c) {return doToC(c);};
4.
C& doToC(C&);
C doToC2(C& c) {return doToC(c);};
无论哪一种,其实都隐式包含了一个拷贝构造函数的调用,比如1中参数c由拷贝构造函数生成,3中doToC的返回值是一个由拷贝构造函数生成的匿名对象。因为我们调用了C的拷贝构造函数,所以以上无论那种情形都需要知道C的定义。
9和10都一样,我们都不需要知道C的定义,只是10的情况下,前置声明的语法会稍微复杂一些。
最后给出一个完整的例子,我们可以看到在两个不同名字空间的类型A和C,A是如何使用前置声明来取代直接包括C的头文件的:
A.h
#pragma once
#include <list>
#include <vector>
#include <map>
#include <utility>
//不同名字空间的前置声明方式
namespace test1
{
class C;
}
namespace test2
{
//用using避免使用完全限定名
using test1::C;
class A
{
public:
C useC(C);
C& doToC(C&);
C& doToC2(C& c) {return doToC(c);};
private:
std::list<C> _list;
std::vector<C> _vector;
std::map<C, C> _map;
C* _pc;
C& _rc;
};
}
C.h
#ifndef C_H
#define C_H
#include <iostream>
namespace test1
{
class C
{
public:
void print() {std::cout<<"Class C"<<std::endl;}
};
}
#endif // C_H
相关文章推荐
- Mars Chen的Broadcast Message Reciver实例总结
- HDU 5233 Gunner II (二分)
- hdu4768Flyer(二分查找)
- halcon算子大全
- zzuli OJ 1010: 求圆的周长和面积
- android sdk更新不成功问题
- Java -verbose:gc命令
- 六款值得推荐的android(安卓)开源框架简介。
- Craphics Context详解
- LeetCode:Implement Queue using Stacks
- axia2+spring配置实例
- 二叉搜索树
- 真正意义上下一代 Windows Embedded:有关 Windows 10 "Athens" 的事
- Android Studio App设置线性布局LinerLayout控件垂直/水平方向排列
- LLVM Bitcode File Format
- zzuli OJ 1009: 求平均分
- Note For Linux By Jes(16)-软件安装:原始码与 Tarball
- android king
- Apache+tomcat集群
- Ubuntu安装svn