跳转至

9 Miscellaneous Topics

文本统计:约 1616 个字 • 242 行代码

9.1 Name casts

The C-style cast is:

  • dangerous because it can do logically different conversion. C-style 转换可以执行多种不同逻辑含义的转换,而且编译器不会告诉你它到底做了哪一种转换。

  • not search-friendly C-style 转换可以执行多种不同逻辑含义的转换,而且编译器不会告诉你它到底做了哪一种转换。

static_cast

基本数据类型之间的转换

适用于数值类型之间的转换,比如 int → double, double → int, char → int 等。

double d = 3.14;
int i = static_cast<int>(d); // i = 3

float f = static_cast<float>(i); // i = 3 → float

向上转型(upcast)

将派生类指针/引用转换为基类指针/引用。这是安全且隐式允许的,但也可以显式使用 static_cast

class Base {};
class Derived : public Base {};

Derived d;
Base* b = static_cast<Base*>(&d); // 合法且安全

向下转型(downcast),但不推荐

将基类指针/引用转换为派生类指针/引用。这在某些情况下可以使用 static_cast,但必须确保对象确实是该类型,否则会导致未定义行为。

Base* b = new Derived();
Derived* d = static_cast<Derived*>(b);
  • 如果 b 实际上指向的是 Base 而不是 Derived,这个转换是非法的。
  • 推荐使用 dynamic_cast 来做这种转换,它会在运行时检查类型是否匹配。

有转换构造函数或类型转换运算符的类之间的转换

如果一个类提供了从其他类型构造的能力(转换构造函数),或者提供了类型转换运算符(operator T()),可以用 static_cast 显式调用这些转换。

class MyClass {
public:
    MyClass(int x) { /* 构造函数 */ }
};

MyClass obj = static_cast<MyClass>(42); // 调用转换构造函数
class MyInt {
private:
    int value;
public:
    MyInt(int v) : value(v) {}
    operator int() const { return value; } // 类型转换运算符
};

MyInt mi(100);
int x = static_cast<int>(mi); // 使用类型转换运算符

枚举与整数之间的转换

static_cast 可以在枚举类型和其底层整数类型之间进行转换。

enum Color { Red, Green, Blue };

Color c = Red;
int i = static_cast<int>(c); // i = 0

int j = 1;
Color c2 = static_cast<Color>(j); // j = 1 → Green

const_cast

const_cast 是 C++ 中用于移除或添加变量的 constvolatile 属性的一种类型转换操作符。它主要用于处理那些在某些情况下需要修改原本被声明为 const 的对象的情况。

type* const_cast<type*>(expression);
type& const_cast<type&>(expression);

这里,expression 必须是指向或者引用一个对象的指针或引用,并且目标类型 type 必须与 expression 的类型相同,除了可能缺少 constvolatile 限定符。


移除 const 属性

最常见的用途是从 const 指针或引用中移除 const 属性,以便可以调用非 const 函数或直接修改数据。

void modify(int* ptr) {
    *ptr = 10; // 修改指针指向的数据
}

int main() {
    const int a = 5;
    const int* p = &a;

    // 移除 const 属性
    int* q = const_cast<int*>(p);

    // 注意:下面的操作是未定义行为,因为原始对象 'a' 被声明为 const
    // *q = 10; 

    // 安全的做法应该是确保原始对象不是真正的 const
    int b = 5;
    const int* pb = &b;
    int* qb = const_cast<int*>(pb);
    *qb = 10; // 安全,因为 'b' 不是真正的 const

    return 0;
}

reinterpret_cast

reinterpret_cast 主要有三种用途

  • 改变指针或者引用的类型
  • 将指针或者引用转换成为足够长的整形
  • 将整型编程指针或者引用类型
int a = 7;
double* p;
p = (double*) &a; // ok (but a is not a double)
p = static_cast<double*>(&a); // error
p = reinterpret_cast<double*>(&a); // ok: I really
 // mean it

dynamic_cast

dynamic_cast 是 C++ 中用于安全向下转型(downcasting)和跨继承层次结构转换的一种类型转换操作符。与 static_cast 不同,dynamic_cast 在运行时进行类型检查,确保转换的安全性。它主要用于处理多态类(即包含至少一个虚函数的类)。

target_type* result = dynamic_cast<target_type*>(expression);
target_type& result = dynamic_cast<target_type&>(expression);

这里 target_type 是你希望转换到的目标类型,而 expression 是要转换的表达式。dynamic_cast 仅适用于指针或引用类型的转换,并且只有当基类至少有一个虚函数时才有效。

跟其他几个不同,其他几个都是编译时完成的,dynamic_cast 是在运行时进行类型检查的

不能用于内置的基本数据类型的强制转换

如果成功的,将返回指向类的指针或者引用,转换失败的话会返回NULL


从基类指针/引用向下转型为派生类指针/引用

这是 dynamic_cast 最常见的用途,用来确保在向下转换时目标对象确实是指定的派生类类型。

class Base {
public:
    virtual ~Base() {} // 确保 Base 是一个多态类
};

class Derived : public Base {};

int main() {
    Base* basePtr = new Derived;
    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);

    if (derivedPtr) {
        std::cout << "Successfully cast to Derived." << std::endl;
    } else {
        std::cout << "Cast failed." << std::endl;
    }

    delete basePtr;
    return 0;
}
  • 如果 basePtr 实际上指向的是 Derived 类型的对象,那么转换成功。
  • 如果 basePtr 指向的是其他类型的对象,或者根本不是 Derived 类型,则返回 nullptr(对于指针)或抛出 std::bad_cast 异常(对于引用)。

来看一段代码

int main()
{
    A a;
    B b;
    A *ap = &a;
    if(dynamic_cast<B*>(ap)) {
        cout << "OK1" << endl;
    }
    else {
        cout << "Fail" << endl;
    }
    if(static_cast<B*>(ap)) {
        cout << "OK2" << endl;
    }
    else {
        cout << "Fail" << endl;
    }
    ap = &b;
    if(dynamic_cast<B*>(ap)) {
        cout << "OK3" << endl;
    }
    else {
        cout << "Fail" << endl;
    }
    if(static_cast<B*>(ap)) {
        cout << "OK4" << endl;
    }
    else {
        cout << "Fail" << endl;
    }
    return 0;
}

运行的结果是第一个失败,其他的都成功

推测导致这个结果的原因:

  • 当ap指向派生类的时候,进行强制类型转换变成派生类是可以成功的
  • 当ap指向基类的时候,dynamic_cast 转换是否成功取决于指针指向的类型和即将转换的类型是不是一样,不一样就会失败,返回一个NULL,而static是可以成功的
  • 其实是更安全的机制导致dynamic的检查更多,要求更高

9.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 包括了 MTS 和 Temporary 的全部属性

  • name, id
  • degree_info
  • employer

关于菱形继承,会造成基类成员重复等问题,引入虚继承的解决方法

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; // OK 
     m.m_i++; // OK, there is only one B1 in m 
     B1* p = new M; // OK 
}

由于菱形继承会有很多麻烦,一般只用于接口类.

9.3 namespace

如果多个头文件(如 old1.hold2.h)都定义了相同名称的函数(例如 f()g()),并且这些函数被包含到同一个源文件中,就会发生命名冲突。这种冲突会导致编译器无法确定应该使用哪个版本的函数,从而引发错误。

// old1.h 
void f();
void g();
// old2.h 
void f();
void g();

为了避免命名冲突,可以将这些函数放入不同的命名空间中。

// old1.h
namespace old1 { 
 void f();
 void g();
}
// old2.h
namespace old2 {
 void f();
 void g();
}

using 指令的作用:使命名空间中的所有名称可用using 指令可以将指定命名空间中的所有标识符(如函数、类、变量等)引入当前作用域,使得在后续代码中可以直接使用这些标识符,而不需要通过命名空间限定符来访问。

// Mylib.h
namespace MyLib {
    void foo();
    class Cat {
        public:
        void Meow();
    };
}
int main() {
    using namespace std;
    using namespace MyLib;

    foo();
    Cat c;
    c.Meow();
    cout << "hello" << endl;
}

using-directives may create potential ambiguities

// Mylib.h 
namespace XLib { 
 void x(); 
 void y(); 
} 
namespace YLib { 
 void y(); 
 void z(); 
}
int main() { 
 using namespace XLib; 
 using namespace YLib; 
 x(); // OK 
 y(); // Error: ambiguous 
 XLib::y(); // OK, resolves to XLib 
 z(); // OK 
}

过短的命名空间名称可能引起冲突:如果命名空间名称太短,可能会与其他命名空间名称发生冲突。例如,两个不同的库都使用了 ns 这个简短的命名空间名称,这将导致编译错误。

过长的命名空间名称难以使用:如果命名空间名称太长,每次使用时都需要输入完整的名称,这会增加代码的复杂性和可读性问题。

为了解决上述问题,可以使用命名空间别名(namespace alias)。命名空间别名允许我们为一个较长或复杂的命名空间创建一个更简洁、易用的别名。

namespace supercalifragilistic {
    void f();
}

namespace short_ns = supercalifragilistic;
short_ns::f();

命名空间也可以进行组合,如果出现冲突,那么通过 using 进行显式地定义

namespace first { 
 void x(); 
 void y(); 
} 
namespace second { 
 void y(); 
 void z(); 
}
namespace mine { 
 using namespace first; 
 using namespace second; 
 using first::y; // resolve clashes
 void mystuff(); 
 /* ... */
}
int main() {
 mine::x();
 mine::y(); // call first::y()
 mine::mystuff();
}

namespace 也是开放的,可以在不同的头文件中定义同一个 namespace

// header1.h 
namespace X { 
 void f(); 
} 
// header2.h 
namespace X { 
 void g(); // X how has f() and g(); 
}

于是我可以为 std 命名空间下做某些函数的特化

namespace mylib {
    class Widget { /* ... */ };
}
// Specialize std::hash so that Widget can be used as
// a key in std::unordered_set and std::unordered_map.
namespace std {
     template<>
     struct hash<mylib::Widget> {
         size_t operator()(const mylib::Widget& obj) const {
            return /* ... */;
         }
     };
}

评论区

对你有帮助的话请给我个赞和 star => GitHub stars
欢迎跟我探讨!!!