当前位置: 首页 > news >正文

微信网站建设哪家好中国行业数据分析网

微信网站建设哪家好,中国行业数据分析网,同一域名可以做相同网站吗,找谁做网站C中为什么要引入make_shared,它有什么优点 1. 减少内存分配次数 使用 make_shared 时,内存分配只发生一次,它同时分配了对象和控制块(用于管理引用计数等信息)。而如果直接使用 new 创建对象并传递给 shared_ptr,则会…

C++中为什么要引入make_shared,它有什么优点

1. 减少内存分配次数

使用 make_shared 时,内存分配只发生一次,它同时分配了对象和控制块(用于管理引用计数等信息)。而如果直接使用 new 创建对象并传递给 shared_ptr,则会分别进行两次内存分配:一次为对象本身,另一次为控制块。

// 使用 new
std::shared_ptr<MyClass> ptr1(new MyClass());// 使用 make_shared
std::shared_ptr<MyClass> ptr2 = std::make_shared<MyClass>();

在上面的例子中,ptr1 进行了一次对象的内存分配和一次控制块的内存分配,而 ptr2 只进行了一次内存分配。

2. 提高性能

由于 make_shared 只进行一次内存分配,它可以减少内存分配和释放的开销,尤其是在频繁创建和销毁对象的场景下。这样可以提高程序的性能。

3. 避免内存泄漏

使用 make_shared 可以减少内存泄漏的风险。因为它提供了一种更安全的方式来创建 shared_ptr,避免了在使用 new 时可能忘记释放内存的情况。使用 make_shared,即使在构造过程中发生异常,内存也会被正确管理。

4.简化代码

使用make_shared创建代码可以简化创建shared_ptr实例的代码,让代码逻辑更加清晰

shared_ptr相关的知识点

  1. shared_ptr基础:与之对应的是unique_ptr,但使用它有限制,比如不能共享所有权。而std::shared_ptr是一个可以共享控制权的智能指针,可以自动管理动态分配的对象生命周期。

  2. new和shared_ptr:在没有make_shared之前,我们通常这样创建shared_ptr:std::shared_ptr<int> sp(new int(5));。这个过程其实做了两个动作:创建一个临时对象,又创建一个shared_ptr对象。如果第一步的内存分配成功,但第二步抛出异常,那么就会发生内存泄漏。

  3. make_shared内部原理:make_shared将对象的动态内存和控制块内存(存储引用计数的那块内存)一次性分配,减少了内存分配的次数。

        例如:auto sp = std::make_shared<int>(5);,这种方式比前一种方式高效,并且更加安全。

  1. 性能更好:单次内存分配意味着分配器只调用一次,这比多次调用(可能导致的内存碎片问题)更加高效。此外,这种方式在多线程环境中也有一定优势,减少了分配内存时的竞争。

  2. 异常安全:使用make_shared,如果在创建过程中抛出异常,因为它是“全有或全无”的过程,所以不需要担心部分资源分配成功导致的内存泄漏。例如,make_shared可以保证在对象和控制块都构建成功之后才开始使用它们。

C++的string内部使用的是堆内存还是栈内存

这个题目主要考察的就是string内部的一个优化,首先std::string这个对象本身是保存在栈内存的,单这个对象所管理的数据是保存在堆中的,但是在string中存在一个短字符串优化(SSO),这个优化让字符串比较小时(字符串个数少于一个值时)会将这个数据对象也存在在string对象的缓冲区中,此时这个对象管理的数据就在栈中。而当字符串的长度超过了这个值时,会将管理的数据放入到堆的空间中。此时这个对象管理的数据就在栈中。 

说明C++中的future,promise,packaged_task,async的区别

1. std::future

  • 定义std::future 是一个用于获取异步操作结果的对象。它可以从一个异步任务中获取结果,并且可以在结果可用时进行等待。
  • 用途: 用于获取异步操作的结果,通常与 std::promisestd::packaged_task 或 std::async 一起使用。
  • 特性:
    • 可以通过 get() 方法获取结果,如果结果尚不可用,它会阻塞当前线程直到结果可用。
    • 可以检查结果是否已经准备好(通过 valid() 和 wait_for() 等方法)。

2. std::promise

  • 定义std::promise 是一个用于设置异步操作结果的对象。它可以与 std::future 结合使用,将结果从一个线程传递到另一个线程。
  • 用途: 用于在一个线程中设置值,另一个线程可以通过 std::future 获取这个值。
  • 特性:
    • 通过 set_value() 方法设置结果,或者通过 set_exception() 方法设置异常。
    • 创建与 promise 关联的 future 对象,使用 get_future() 方法。

3. std::packaged_task

  • 定义std::packaged_task 是一个可调用对象,它可以封装任何可调用的任务(如函数、lambda 表达式等)并将其与 std::future 关联。
  • 用途: 用于将任务与一个 future 关联起来,使得可以异步执行这个任务并获取结果。
  • 特性:
    • 可以通过 operator() 调用封装的任务。
    • 通过 get_future() 方法获取与 packaged_task 关联的 future 对象。
    • 适合于需要将任务封装并在不同线程中执行的场景。

4. std::async

  • 定义std::async 是一个用于异步执行函数的标准库函数。它返回一个 std::future,可以用来获取异步操作的结果。
  • 用途: 用于简单地启动异步任务并获取结果,通常不需要手动管理线程。
  • 特性:
    • 可以指定任务的执行策略(如 std::launch::async 或 std::launch::deferred)。
    • 自动管理线程,用户不需要显式地创建和管理线程。
    • std::launch::async: 任务立即在新线程中执行,适合需要并发执行的场景。
    • std::launch::deferred: 任务延迟到调用 get() 时执行,适合轻量任务或可能不会执行的情况。

总结

组件定义用途主要特性
std::future获取异步操作结果的对象获取异步操作的结果阻塞等待结果,检查结果状态
std::promise设置异步操作结果的对象在线程中设置值,另一个线程获取设置值或异常,创建与 future 关联的对象
std::packaged_task可调用对象,封装任务与 future 关联封装任务并异步执行,获取结果封装任意可调用对象,调用后可以获取结果
std::async异步执行函数的标准库函数简单启动异步任务并获取结果自动管理线程,支持执行策略

简化之后的说明如下:

  1. std::future:用于在不同线程间传递结果。它可以从异步操作中获取返回值。

  2. std::promise:用于设置一个值或者异常,这些值或异常会被对应的 std::future 获取。

  3. std::packaged_task:包装一个可调用对象(函数、lambda表达式、bind表达式等),方便异步执行,并将结果通过 std::future 获取。

  4. std::async:用于异步地启动任务,并返回一个 std::future 对象以便获取任务结果。

这些特性在不同场合下分别发挥各自的作用:

  • std::future 和 std::promise 更适用于实现自定义的异步操作。
  • std::packaged_task 更适合将现有的函数包装为异步任务,而无需显式使用线程。
  • std::async 最为简便,用于从简化的异步调用中获取结果。

 C++的async在使用的时候有什么注意事项 

  1. 选择合适的启动策略(launch policy)std::async 可以接受一个启动策略作为参数,如 std::launch::asyncstd::launch::deferredstd::launch::async 会创建一个新的线程,而 std::launch::deferred 只会在需要结果时才开始任务。所以,要根据实际需求选择合适的策略。注意,一定要明确指定创建策略。如果不明确指定创建策略,以上两个都不是 async 的默认策略,而是 undefined,它是一个基于任务的程序设计,内部有一个调度器(线程池),会根据实际情况决定采用哪种策略。

  2. 保证获取结果(future.get)std::async 返回一个 std::future 对象,一定要记得调用 future.get() 或者 future.wait() 来获取任务结果或者等待任务完成。如果没有获取结果,程序的行为是不确定的。

  3. 异常处理:当异步任务中抛出异常时,这些异常会被 future.get() 捕获。所以,一定要在调用 future.get() 时准备好捕获和处理可能的异常。

  4. 确认 async 对象的生命周期:如果从 std::async 获得的 std::future 未被移动或绑定到引用,则在完整表达式结尾这个std::future对象会被销毁。

扩展知识:

  • 返回值的类型推导:std::async 会自动推导返回值类型,而且会在异步任务完成后把结果存储在 std::future 对象里。这种类型推导可以让代码更加简洁,不需要明确指定返回值类型。

  • 任务的生命周期:异步任务的生命周期与 std::future 对象绑定。如果 future 对象被销毁,那么异步任务也会被取消。所以,确保 future 对象的生命周期覆盖任务的执行时间。

  • 共享结果:std::shared_future:有时候你可能需要多个线程访问同一个异步结果,这时可以使用 std::shared_future。它可以让多个线程共享异步结果,而不是使用单一的 std::future,确保多线程访问时的安全性。

  • 并发:虽然 std::async 提供了一种简单实用的并发机制,但在实际应用中,你可能还需要使用其他并发容器如 std::mutexstd::lock_guardstd::atomic 等来处理复杂的共享数据访问问题。

二叉搜索树的删除逻辑 

在二叉搜索树(Binary Search Tree, BST)中,删除节点的逻辑相对复杂,因为我们需要保持树的性质。删除节点的过程可以分为三种主要情况:

  1. 删除的节点是叶子节点(没有子节点)
  2. 删除的节点有一个子节点
  3. 删除的节点有两个子节点

删除逻辑

1. 删除叶子节点

直接将该节点从树中移除。

2. 删除有一个子节点的节点

将该节点的父节点指向该节点的唯一子节点,从而移除该节点。

3. 删除有两个子节点的节点

此时,我们需要找到该节点的后继节点(在右子树中最小的节点)或前驱节点(在左子树中最大的节点),然后用该节点的值替换要删除的节点的值,并递归地删除该后继或前驱节点。

C++ 实现

以下是一个简单的 C++ 实现,展示了二叉搜索树的删除逻辑:

#include <iostream>struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};class BST {
public:TreeNode* root;BST() : root(nullptr) {}// 插入节点TreeNode* insert(TreeNode* node, int val) {if (!node) return new TreeNode(val);if (val < node->val)node->left = insert(node->left, val);elsenode->right = insert(node->right, val);return node;}// 删除节点TreeNode* deleteNode(TreeNode* root, int key) {if (!root) return nullptr;if (key < root->val) {root->left = deleteNode(root->left, key);} else if (key > root->val) {root->right = deleteNode(root->right, key);} else {// 找到要删除的节点if (!root->left) {return root->right; // 只有右子树} else if (!root->right) {return root->left; // 只有左子树} else {// 有两个子节点,找后继节点TreeNode* successor = minValueNode(root->right);root->val = successor->val; // 替换值root->right = deleteNode(root->right, successor->val); // 删除后继节点}}return root;}// 找到右子树中的最小节点TreeNode* minValueNode(TreeNode* node) {TreeNode* current = node;while (current && current->left) {current = current->left;}return current;}// 中序遍历(用于测试)void inorder(TreeNode* node) {if (node) {inorder(node->left);std::cout << node->val << " ";inorder(node->right);}}
};int main() {BST tree;tree.root = tree.insert(tree.root, 50);tree.insert(tree.root, 30);tree.insert(tree.root, 20);tree.insert(tree.root, 40);tree.insert(tree.root, 70);tree.insert(tree.root, 60);tree.insert(tree.root, 80);std::cout << "Inorder traversal of the BST: ";tree.inorder(tree.root);std::cout << std::endl;std::cout << "Delete 20\n";tree.root = tree.deleteNode(tree.root, 20);std::cout << "Inorder traversal after deleting 20: ";tree.inorder(tree.root);std::cout << std::endl;std::cout << "Delete 30\n";tree.root = tree.deleteNode(tree.root, 30);std::cout << "Inorder traversal after deleting 30: ";tree.inorder(tree.root);std::cout << std::endl;std::cout << "Delete 50\n";tree.root = tree.deleteNode(tree.root, 50);std::cout << "Inorder traversal after deleting 50: ";tree.inorder(tree.root);std::cout << std::endl;return 0;
}

C++什么场景下使用锁(lock)?什么场景下使用原子变量(atomic)?

锁(lock)和原子变量(atomic)都可以用作同步机制,它们有各自的适用场景:

1) 使用锁的场景:

  • 当需要保护长时间访问的临界区时,比如复杂的操作或逻辑(如链表、树等复杂数据结构的操作)。
  • 当多个共享资源需要同步访问时,锁可以一次性锁定多个资源,确保整体一致性。
  • 在涉及到复杂的操作时,比如需要一次性更新多个共享变量。

2) 使用原子变量的场景:

  • 当操作可以在一个原子步骤内完成时,比如简单的整数增减、标志位切换。
  • 当性能非常关键,且锁的开销和上下文切换的成本过高时。原子操作通常比使用锁更轻量级。
  • 用于实现非阻塞算法时,因为原子变量不会导致线程挂起而等待锁释放。

建议:优先使用原子变量,如果发现使用原子变量不能满足同步机制的需求,那就使用锁。

使用锁来保护的例子:

std::mutex mtx;
std::map<int, std::string> sharedMap;void insertIntoMap(int key, const std::string& value) {std::lock_guard<std::mutex> lock(mtx);sharedMap[key] = value;
}

使用原子变量的例子:

class AtomicCounter {
public:AtomicCounter() : count(0) {}// 增加计数void increment() {count.fetch_add(1, std::memory_order_relaxed);}// 获取当前计数int get() const {return count.load(std::memory_order_relaxed);}private:std::atomic<int> count; // 原子变量
};

扩展知识:

1) 锁的类型:

  • 互斥锁(Mutex):最常见的普通锁,用于保护一个共享资源。
  • 读写锁(Read-Write Lock):允许多个读者并行访问,但写者访问需要独占。
  • 自旋锁(Spinlock):线程在等待时会不断轮询锁状态,而不是挂起,非常适合短时间持有锁的场景。

2) 原子操作:

  • 可以使用 std::atomic 库提供的原子类型,如 std::atomic<int>std::atomic<bool>atomic 是个模板类,你还可以使用 double 等类型填充。
  • 这些操作通常由硬件直接支持,比如 x86 架构的 "Lock" 前缀指令,确保读取-修改-写入一个不可分割的操作。

C++中锁的底层原理是什么

1. 互斥锁(Mutex)

互斥锁是最常见的锁类型,用于保护共享数据。C++11 引入了 <mutex> 头文件,提供了 std::mutex 类。

  • 实现原理
    • 原子操作:互斥锁的实现依赖于原子操作,例如测试并设置(Test-and-Set)或比较并交换(Compare-and-Swap,CAS)。这些操作通常由 CPU 提供,并且是线程安全的。
    • 系统调用:在用户空间中,互斥锁的状态(锁定或未锁定)通常用一个标志位表示。当一个线程尝试获取锁时,它会检查这个标志位。如果锁未被占用,线程可以成功获取锁并将标志位设置为已锁定。如果锁已被占用,线程可能会进入阻塞状态,直到锁被释放。
    • 调度:操作系统负责调度和唤醒线程。当一个线程释放锁时,操作系统会选择一个等待线程来获取锁。

2. 自旋锁(Spinlock)

自旋锁是一种轻量级锁,适用于锁持有时间非常短的场景。

  • 实现原理
    • 自旋锁使用原子操作来检查锁的状态。如果锁未被占用,线程会通过原子操作获取锁;如果锁已被占用,线程会不断循环(自旋)检查锁的状态,直到锁可用。
    • 自旋锁避免了线程上下文切换的开销,但在持锁时间较长时可能会导致 CPU 资源浪费。

3. 读写锁(Read-Write Lock)

读写锁允许多个线程同时读取共享资源,但在写入时会阻塞其他线程。

  • 实现原理
    • 读写锁通常使用一个计数器来跟踪当前的读操作数量和一个互斥锁来保护写操作。
    • 当一个线程请求写锁时,它会检查当前是否有读锁或写锁被持有。如果有,写锁请求将被阻塞;如果没有,线程可以获得写锁。
    • 当一个线程请求读锁时,它会检查当前是否有写锁被持有。如果没有,线程可以获得读锁,并增加读锁计数器。

4. 条件变量(Condition Variable)

条件变量用于线程间的同步,允许一个或多个线程在某个条件发生时被唤醒。

  • 实现原理
    • 条件变量通常与互斥锁一起使用。线程在等待条件变量时会释放互斥锁,并在条件满足时被唤醒。
    • 条件变量的实现通常依赖于操作系统提供的信号量或其他同步原语。

5. 内存屏障(Memory Barrier)

在多线程环境中,内存屏障用于确保特定的内存操作顺序,以避免编译器和 CPU 的优化导致的数据不一致。

  • 实现原理
    • 内存屏障可以防止编译器重排代码,确保在屏障之前的所有操作在屏障之后的操作之前完成。
    • 在 C++ 中,可以使用原子操作和内存序(memory order)来控制这些操作的顺序。
http://www.hotlads.com/news/5253.html

相关文章:

  • 在线简历制作网站免费陕西seo排名
  • 中小学图书馆网站建设百度地图在线查询
  • 东城住房和城乡建设委员会网站百度关键词指数查询
  • 移动端网站建设重点有哪些产品推广方式及推广计划
  • 专门做捷径网站百度认证服务平台
  • 女和男做搞基视频网站搜索引擎优化的重要性
  • 网站建设运行状况上海百网优seo优化公司
  • 南宁门户网站有哪些seo优化方法
  • 江宁区建设工程局网站专门做排行榜的软件
  • 站长网网站模板杭州优化商务服务公司
  • 公司怎么搭建自己网站写手接单平台
  • 个人做的网站能备案吗日本今日新闻头条
  • 电子商务网站的建设和维护论文汕头网站排名
  • 网站建设服务合同书google google
  • 做优化的网站必须独立IP吗东营seo
  • 网站特效怎么做自适应电商运营培训大概多少学费
  • 山西省住房建设厅网站首页搜索引擎营销的主要方式有
  • 网站群管理全网营销是什么
  • 网站建设外包协议中国进入全国紧急状态
  • 网站域名被劫持百度seo关键词优化排行
  • 设计欣赏网站深圳seo优化培训
  • 网页设计与网站建设docx快速优化官网
  • 做网站对电脑要求高吗上海做推广的引流公司
  • 机械加工网站色彩搭配自己有域名怎么建网站
  • 做网站运营的要求排行榜123网
  • 开封市建设中专继续教育网站企业软文代写
  • wordpress 调查问卷爱站网站seo查询工具
  • js做各类图表网站查询网域名查询
  • 药品行业做网站创建网站的基本流程
  • 学校如何建网站西点培训学校