c++类型转换、智能指针以及void *的讲解

简介

  • c++ c++中的类型转换: const_cast,static_cast,dynamic_cast,reinterpret_cast
  • c++中的智能指针:auto_ptr、shared_ptr、weak_ptr、 unique_ptr,
  • void *之间的区别

1. C++ 类型转换

const_cast

 - **作用**:用于去除指针或引用的`const`或`volatile`限定符。
 - **示例代码**:
      #include <iostream>
      void func(const int* ptr) {
          // 错误,不能直接修改const指针指向的值
          // *ptr = 10;
          int* nonConstPtr = const_cast<int*>(ptr);
          *nonConstPtr = 10;
      }
      int main() {
          int num = 5;
          const int* constPtr = &num;
          std::cout << "Before func, num = " << num << std::endl;
          func(constPtr);
          std::cout << "After func, num = " << num << std::endl;
          return 0;
      }
 - **使用场景**:当你确定某个对象不会因为修改而导致问题,
 - 但函数参数或返回值是`const`限定的,需要临时去除`const`来进行修改时使用。
 - 不过,滥用`const_cast`可能会导致程序出现未定义行为,所以要谨慎使用。

static_cast

 - **作用**:用于在相关类型之间进行转换,例如基本数据类型转换、类层次结构中的向上转换(将派生类指针或引用转换为基类指针或引用)。
 - **示例代码(基本数据类型转换)**:
      #include <iostream>
      int main() {
          double numDouble = 3.14;
          int numInt = static_cast<int>(numDouble);
          std::cout << "Converted value: " << numInt << std::endl;
          return 0;
      }
 - **示例代码(类层次结构向上转换)**:
       #include <iostream>
       class Base {
       public:
           virtual void print() {
               std::cout << "This is Base class." << std::endl;
           }
       };
       class Derived : public Base {
       public:
           void print() override {
               std::cout << "This is Derived class." << std::endl;
           }
       };
       int main() {
           Derived derivedObj;
           Base* basePtr = static_cast<Base*>(&derivedObj);
           basePtr->print();
           return 0;
       }
 - **使用场景**:在已知类型之间安全地进行转换,如将一种算术类型转换为另一种算术类型,或者在类层次结构中进行向上转换(当派生类对象的行为可以被基类行为所包含时)。

dynamic_cast

 - **作用**:主要用于在类层次结构中的向下转换(将基类指针或引用转换为派生类指针或引用),并且在运行时进行类型检查。
  • 示例代码
      #include <iostream>
      class Base {
      public:
          virtual void print() {
              std::cout << "This is Base class." << std::endl;
          }
      };
      class Derived : public Base {
      public:
          void print() override {
              std::cout << "This is Derived class." << std::endl;
          }
      };
      int main() {
          Base* basePtr = new Derived();
          // 进行动态转换
          Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
          if (derivedPtr) {
              derivedPtr->print();
          } else {
              std::cout << "Dynamic cast failed." << std::endl;
          }
          delete basePtr;
          return 0;
      }
 - **使用场景**:当你有一个指向基类的指针或引用,并且不确定它实际指向的是哪个派生类对象,需要在运行时进行检查并进行向下转换时使用。这在处理多态对象时非常有用,例如在一个图形绘制系统中,根据不同的图形类型(圆形、矩形等派生类)进行不同的绘制操作。

reinterpret_cast

 - **作用**:可以进行几乎任意类型之间的转换,包括一些在语义上不相关的类型转换,它只是简单地重新解释二进制数据。
  • 示例代码
       #include <iostream>
       int main() {
           int num = 10;
           // 将int*转换为char*,这只是重新解释内存布局
           char* charPtr = reinterpret_cast<char*>(&num);
           std::cout << "Memory representation as characters: ";
           for (int i = 0; i < sizeof(int); ++i) {
               std::cout << charPtr[i];
           }
           std::cout << std::endl;
           return 0;
       }
 - **使用场景**:在一些非常底层的操作中,如与硬件接口或者处理二进制数据格式时使用。不过,这种转换很危险,因为它可能会导致不可预测的结果,除非你非常清楚自己在做什么。

2. C++ 智能指针

auto_ptr(已弃用)

-示例代码(不推荐使用)

      #include <iostream>
      #include <memory>
      class MyClass {
      public:
          MyClass() {
              std::cout << "MyClass constructor called." << std::endl;
          }
          ~MyClass() {
              std::cout << "MyClass destructor called." << std::endl;
          }
          void printMessage() {
              std::cout << "This is a message from MyClass." << std::endl;
          }
      };
      int main() {
          std::auto_ptr<MyClass> autoPtr(new MyClass);
          autoPtr->printMessage();
          // 注意:auto_ptr存在所有权转移问题,不推荐使用
          return 0;
      }
 - **使用场景(已不推荐)**:`auto_ptr`曾经用于自动管理动态分配的对象的内存,但它有一个严重的问题,
 - 即当进行复制操作时会转移所有权,这可能导致意外的行为。
 - 现在已经被`unique_ptr`等更好的智能指针所替代。

shared_ptr

  • 示例代码
      #include <iostream>
      #include <memory>
      class AnotherClass {
      public:
          AnotherClass() {
              std::cout << "AnotherClass constructor called." << std::endl;
          }
          ~AnotherClass() {
              std::cout << "AnotherClass destructor called." << std::endl;
          }
          void showMessage() {
              std::cout << "This is a message from AnotherClass." << std::endl;
          }
      };
      int main() {
          std::shared_ptr<AnotherClass> sharedPtr1(new AnotherClass);
          std::shared_ptr<AnotherClass> sharedPtr2 = sharedPtr1;
          // 引用计数为2
          sharedPtr1->showMessage();
          sharedPtr2->showMessage();
          // 当最后一个指向该对象的shared_ptr离开作用域时,对象才会被销毁
          return 0;
      }
  • 使用场景:当多个对象或者代码部分需要共享同一个资源的所有权时使用。例如,在一个复杂的数据结构中,多个节点可能都需要引用同一个子对象。只要有一个shared_ptr引用这个对象,它就不会被销毁,直到所有引用都消失。比如在一个缓存系统中,多个用户可能同时访问和使用同一个缓存对象,就可以用shared_ptr来管理这个缓存对象的内存。

std::shared_ptr的线程安全考虑与实现方式

引用计数的线程安全
原理:std::shared_ptr的引用计数操作本身是原子操作(在标准库的实现中),这意味着多个线程同时对std::shared_ptr进行拷贝构造或者赋值操作(这些操作会改变引用计数)是安全的。

示例代码:
#include <iostream>
#include <memory>
#include <thread>
#include <vector>

void func(std::shared_ptr<int> ptr) {
    // 多个线程同时访问这个函数,对shared_ptr进行拷贝
    // 引用计数的原子操作保证线程安全
    std::shared_ptr<int> localPtr = ptr;
    std::cout << "Thread " << std::this_thread::get_id() << " - Reference count: " << localPtr.use_count() << std::endl;
}

int main() {
    std::shared_ptr<int> sharedPtr(new int(10));
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; ++i) {
        threads.push_back(std::thread(func, sharedPtr));
    }
    for (auto& th : threads) {
        th.join();
    }
    return 0;
}

对象访问的线程安全

  • 原理:虽然引用计数的操作是线程安全的,但是std::shared_ptr所指向的对象本身的访问不是线程安全的。如果多个线程同时通过std::shared_ptr访问和修改对象的成员,可能会导致数据竞争。可以使用互斥锁(std::mutex)等同步机制来保证对象访问的线程安全。
示例代码:
#include <iostream>
#include <memory>
#include <thread>
#include <mutex>

class Counter {
public:
    Counter() : count(0) {}
    void increment() {
        std::lock_guard<std::mutex> guard(mutex_);
        ++count;
    }
    int getCount() const {
        std::lock_guard<std::mutex> guard(mutex_);
        return count;
    }
private:
    mutable std::mutex mutex_;
    int count;
};

void threadFunction(std::shared_ptr<Counter> counter) {
    for (int i = 0; i < 1000; ++i) {
        counter->increment();
    }
}

int main() {
    std::shared_ptr<Counter> sharedCounter(new Counter);
    std::thread thread1(threadFunction, sharedCounter);
    std::thread thread2(threadFunction, sharedCounter);
    thread1.join();
    thread2.join();
    std::cout << "Final count: " << sharedCounter->getCount() << std::endl;
    return 0;
}

weak_ptr

  • 示例代码(与shared_ptr配合)
    #include <iostream>
    #include <memory>
    class Node {
    public:
        std::shared_ptr<Node> next;
        int data;
        Node(int d) : data(d) {}
    };
    int main() {
        std::shared_ptr<Node> node1(new Node(1));
        std::shared_ptr<Node> node2(new Node(2));
        node1->next = node2;
        std::weak_ptr<Node> weakNode = node1;
        if (std::shared_ptr<Node> lockedNode = weakNode.lock()) {
            std::cout << "The node data is: " << lockedNode->data << std::endl;
        } else {
            std::cout << "The node has been destroyed." << std::endl;
        }
        node1.reset();
        node2.reset();
        if (std::shared_ptr<Node> lockedNode = weakNode.lock()) {
            std::cout << "The node data is: " << lockedNode->data << std::endl;
        } else {
            std::cout << "The node has been destroyed." << std::endl;
        }
        return 0;
    }
    
    • 使用场景:用于解决shared_ptr的循环引用问题。在上述链表的例子中,如果没有weak_ptr,当两个节点相互引用(例如双向链表)且都用shared_ptr时,它们的引用计数永远不会为0,导致内存泄漏。weak_ptr可以用来观察shared_ptr所指向的对象,而不会增加引用计数,从而打破循环引用。

std::weak_ptr的线程安全考虑与实现方式(与std::shared_ptr结合)

观察std::shared_ptr的线程安全操作

  • 原理:std::weak_ptr本身的操作(如lock函数)在和std::shared_ptr的引用计数交互时是线程安全的。因为std::weak_ptr的lock操作涉及到检查std::shared_ptr的引用计数是否大于 0,这个检查是原子操作。
    示例代码:
#include <iostream>
#include <memory>
#include <thread>

void threadFunction(std::weak_ptr<int> weakPtr) {
    std::shared_ptr<int> sharedPtr = weakPtr.lock();
    if (sharedPtr) {
        std::cout << "Thread " << std::this_thread::get_id() << " - Value: " << *sharedPtr << std::endl;
    }
}

int main() {
    std::shared_ptr<int> sharedPtr(new int(20));
    std::weak_ptr<int> weakPtr = sharedPtr;
    std::thread thread1(threadFunction, weakPtr);
    std::thread thread2(threadFunction, weakPtr);
    thread1.join();
    thread2.join();
    return 0;
}

- unique_ptr

 - **示例代码**:
   ```cpp
   #include <iostream>
   #include <memory>
   class MyClass2 {
   public:
       MyClass2() {
           std::cout << "MyClass2 constructor called." << std::endl;
       }
       ~MyClass2() {
           std::cout << "MyClass2 destructor called." << std::endl;
       }
       void printMessage() {
           std::cout << "This is a message from MyClass2." << std::endl;
       }
   };
   int main() {
       std::unique_ptr<MyClass2> uniquePtr(new MyClass2);
       uniquePtr->printMessage();
       // 不能进行复制操作,以下是错误的
       // std::unique_ptr<MyClass2> anotherUniquePtr = uniquePtr;
       // 可以通过std::move进行所有权转移
       std::unique_ptr<MyClass2> anotherUniquePtr = std::move(uniquePtr);
       anotherUniquePtr->printMessage();
       // 当最后一个拥有对象所有权的unique_ptr离开作用域时,对象会被销毁
       return 0;
   }
   ```
 - **使用场景**:当你想要确保一个对象在某个作用域结束时自动被销毁,并且这个对象的所有权是独占的,就适合使用`unique_ptr`。比如在函数内部动态分配了一个资源(如上述代码中的`MyClass2`对象),当函数返回时,希望自动释放这个资源,避免内存泄漏。

std::unique_ptr的线程安全考虑与实现方式

独占所有权的特性与线程安全
  • 原理:由于std::unique_ptr具有独占所有权的特性,在一个时刻只有一个std::unique_ptr拥有对象。如果在多线程环境下,需要确保在传递std::unique_ptr的所有权时是在安全的同步机制下进行的。通常可以使用互斥锁或者原子操作来保证这一点。
  • 示例代码(使用互斥锁传递所有权):
 #include <iostream>
#include <memory>
#include <thread>
#include <mutex>

std::mutex mutex_;
std::unique_ptr<int> uniquePtr;

void threadFunction1() {
    std::lock_guard<std::mutex> guard(mutex_);
    uniquePtr.reset(new int(10));
}

void threadFunction2() {
    std::unique_ptr<int> localPtr;
    {
        std::lock_guard<std::mutex> guard(mutex_);
        localPtr = std::move(uniquePtr);
    }
    if (localPtr) {
        std::cout << "Thread " << std::this_thread::get_id() << " - Value: " << *localPtr << std::endl;
    }
}

int main() {
    std::thread thread1(threadFunction1);
    std::thread thread2(threadFunction2);
    thread1.join();
    thread2.join();
    return 0;
}
  1. void*指针
    • 示例代码
      #include <iostream>
      #include <cstdlib>
      int main() {
          int num = 10;
          void* voidPtr = &num;
          int* intPtr = static_cast<int*>(voidPtr);
          std::cout << "The value of num is: " << *intPtr << std::endl;
          double doubleNum = 3.14;
          voidPtr = &doubleNum;
          double* doublePtr = static_cast<double*>(voidPtr);
          std::cout << "The value of doubleNum is: " << *doublePtr << std::endl;
          return 0;
      }
      
    • 使用场景
      • 通用数据存储和传递:在一些底层的库或者需要高度通用性的代码中,void*可以用来存储和传递不同类型的数据。例如,在一个简单的缓存系统中,缓存的数据类型可能各种各样(整数、浮点数、结构体等),可以用void*来存储这些数据,然后通过额外的标记或者类型信息来在合适的时候进行正确的类型转换和解引用。
      • 与C语言接口交互:C++ 经常需要和C语言代码进行交互。在C语言中,void*指针被广泛用于一些通用的函数接口,如内存分配函数malloc返回的就是void*指针。在C++ 中调用这些C语言函数时,也需要正确地处理void*指针。例如:
        #include <iostream>
        #include <cstdlib>
        int main() {
            // 使用C语言的malloc函数分配内存,返回void*指针
            void* memory = malloc(sizeof(int));
            int* intMemory = static_cast<int*>(memory);
            *intMemory = 5;
            std::cout << "The value stored in allocated memory is: " << *intMemory << std::endl;
            // 释放内存,C++中可以使用free函数(因为它来自C标准库)
            free(memory);
            return 0;
        }
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值