Chapter 15 Miscellaneous Points¶
1 Named Casts¶
为什么不用 C 风格强转?
C 风格 cast 含义过于宽泛,容易做出危险转换,也不方便搜索和维护。
1. static_cast:适合语义上合理、编译器可检查的转换
double d = 7.1;
int a;
a = d; // implicit
a = (int)d; // C-style
a = static_cast<int>(d); // 更明确
2. reinterpret_cast:最危险的一类转换之一
int a = 7;
double* p;
p = (double*)&a; // C-style
// p = static_cast<double*>(&a); // error
p = reinterpret_cast<double*>(&a); // 强行按位解释
3. const_cast:用于增加/去除顶层 const(修改不一定合法)
const int c = 7;
int* q;
// q = &c; // Error:&c 是 const int*,不能直接赋值给普通 int*
q = (int*)&c; // 可行,但后续 *q = 2 会修改 const 常量,属于未定义行为
q = static_cast<int*>(&c); // Error:static_cast 不允许去掉 const,它只做安全的类型转换
q = const_cast<int*>(&c); // I really mean it
4. dynamic_cast:基类必须是多态类型,通常意味着至少有一个虚函数
struct A { virtual void f() {} };
struct B : public A {};
struct C : public A {};
A* pa = new B;
C* pc1 = static_cast<C*>(pa); // OK: but *pa is B!
C* pc2 = dynamic_cast<C*>(pa); // 运行时失败,得到 nullptr
2 Multiple Inheritance¶
示例
class Employee {
protected:
String name;
EmpID id;
};
class MTS : public Employee {
protected:
Degrees degree_info;
};
class Temporary {
protected:
Company employer;
};
class Consultant : public MTS, public Temporary {
// ...
};
Consultant 会同时拿到 name、id、degree_info 和 employer。
普通多继承(Vanilla MI)数据布局复杂,可能复制出多份基类子对象,名称与转换可能变得二义。
Consultant 同时含有 MTS 和 Temporary 两个基类子对象,如果这些基类链上再共享同一个祖先类,就可能出现重复祖先子对象。
示例
struct B1 { int m_i; };
struct D1 : public B1 {};
struct D2 : public B1 {};
struct M : public D1, public D2 {};
M m;
// B1* p = &m; // ERROR: 哪一个 B1?
B1* p1 = static_cast<D1*>(&m);
B1* p2 = static_cast<D2*>(&m);
M m;
// m.m_i++; // ERROR: D1::B1::m_i 还是 D2::B1::m_i ?
若基类只是纯接口类、不含数据状态,复制通常问题不大,因此较安全的用途是组合多个协议/接口类(protocol / interface classes)。
抽象基类需满足以下条件:
- 除析构函数外,所有非静态成员函数均为纯虚函数
- 析构函数为虚函数,且函数体为空
- 不包含任何非静态成员变量,可包含静态成员
示例:接口类
class CDevice {
public:
virtual ~CDevice() {}
virtual int read(...) = 0;
virtual int write(...) = 0;
virtual int open(...) = 0;
virtual int close(...) = 0;
virtual int ioctl(...) = 0;
};
为避免重复基类,可把基类声明为 virtual,在 C++ 语境里,virtual 往往意味着间接访问。
虚基类可以让最底层派生类只保留一份虚基类子对象,代价是实现会通过间接指针访问虚基类部分,因此空间与运行期复杂度都会上升。
iostream 中共享基类的图示

示例:Virtual bases
struct B1 { int m_i; };
struct D1 : virtual public B1 {};
struct D2 : virtual public B1 {};
struct M : public D1, public D2 {};
int main() {
M m;
m.m_i++; // OK,只剩一份 B1
B1* p = new M; // OK
}
多重继承应谨慎使用,尤其避免菱形继承,若只是组合多个接口,优先考虑接口类 + 受控多继承的写法。
如果基类子对象的重复拷贝(复制)不会造成问题,那么就无需将基类声明为虚基类。
抽象基类(除虚表指针 vptr 外不包含任何数据成员)即使被多次复制也不会产生问题,因此这类场景下可以不必使用虚基类。
3 Namespaces¶
命名空间用于避免全局名字冲突,表达逻辑分组,它本身就是一种作用域。
示例
// old1.h
void f();
void g();
// old2.h
void f();
void g();
namespace old1 {
void f();
void g();
}
namespace old2 {
void f();
void g();
}
Defining and Implementing Namespaces
namespace MyLib {
void foo();
class Cat {
public:
void Meow();
};
}
#include "MyLib.h"
void MyLib::foo() {
cout << "foo\n";
}
void MyLib::Cat::Meow() {
cout << "meow\n";
}
- 显式限定:使用命名空间中的名称,繁琐且容易分散注意力。
示例
#include "MyLib.h"
int main() {
MyLib::foo();
MyLib::Cat c;
c.Meow();
}
using声明:为名称引入一个局部别名,消除重复的作用域限定符。
示例
int main() {
using MyLib::foo;
using MyLib::Cat;
foo();
Cat c;
c.Meow();
}
using namespace指令:把整个命名空间中的名字都引入可见范围。
示例
int main() {
using namespace std;
using namespace MyLib;
foo();
Cat c;
c.Meow();
cout << "hello" << endl;
}
using namespace 方便,但可能引入二义性
namespace XLib {
void x();
void y();
}
namespace YLib {
void y();
void z();
}
using namespace XLib;
using namespace YLib;
x(); // OK
// y(); // Error: ambiguous
XLib::y(); // OK
z(); // OK
- 命名空间别名(Namespace aliases):可以通过别名来创建便于使用的名称,也可用于为不同版本的库设置标识。过短可能会发生命名冲突,过长使用不便。
示例
namespace supercalifragilistic {
void f();
}
namespace short_ns = supercalifragilistic;
short_ns::f();
- 命名空间组合(Namespace composition):可以把其他命名空间中的名字重新组织到新命名空间中,
using声明可以解决潜在的命名冲突,显式定义的函数优先级更高。
示例
namespace first {
void x();
void y();
}
namespace second {
void y();
void z();
}
namespace mine {
using namespace first;
using namespace second;
using first::y; // 解决冲突
void mystuff();
}
int main() {
mine::x();
mine::y(); // call first::y()
mine::mystuff();
}
- 命名空间的选择(Namespace selection):只选择你需要的名称,而非引入全部内容。
示例
namespace mine {
using orig::Cat;
void x();
void y();
}
命名空间是开放的:多次声明同一个命名空间会向其中追加内容,命名空间可以分散定义在多个文件中。
示例
// header1.h
namespace X {
void f();
}
// header2.h
namespace X {
void g(); // X how has f() and g();
}