【C++】智能指针 学习总结 |std::shared_ptr |std::unique_ptr | std::weak_ptr
文章目录
- 前言
- 一、智能指针介绍
- 二、普通指针和智能指针的比较案例
- 三、std::shared_ptr
- 四、std::unique_ptr
- 五、std::weak_ptr
- 六、std::shared_ptr |std::unique_ptr | std::weak_ptr三大智能指针的区别
前言
参考答案:chatgpt
一、智能指针介绍
智能指针是C++的一种对象,它可以像常规指针那样,用来指向堆上的数据。然而,智能指针的关键优势在于,**当你不再需要在堆上的数据时,它们可以自动删除。**这是通过在智能指针的析构函数中调用delete来实现的,当智能指针的生命周期结束时(例如,它离开了它的作用域),它的析构函数会被自动调用。
C++标准库提供了几种智能指针:
std::auto_ptr:这是C++98标准的一部分,但在C++11中已被弃用,C++17中已被移除。它具有所有权传递语义,意味着复制auto_ptr会改变原有auto_ptr的状态(会失去指向对象的能力)。
std::unique_ptr:这是C++11引入的智能指针,它表现出独占所有权语义,意味着在任何时间,只有一个unique_ptr可以指向给定的对象。当unique_ptr被销毁(例如,离开其作用域)时,它所指向的对象也会被delete掉。
从 C++14 开始,建议使用 std::make_unique 函数来创建 std::unique_ptr,
auto p = std::make_unique<MyClass>();
std::shared_ptr:这也是C++11引入的智能指针,允许多个shared_ptr指向同一个对象。shared_ptr维护了一个引用计数,当最后一个shared_ptr被销毁(离开其作用域)时,它所指向的对象才会被delete掉。
std::weak_ptr:这也是C++11引入的智能指针,它是对shared_ptr的补充和配合。weak_ptr指向一个由shared_ptr管理的对象,但不会增加该对象的引用计数。这主要用于解决shared_ptr的循环引用问题。
后面三个智能指针用的比较多。
使用智能指针可以帮助管理动态内存,避免内存泄漏。然而,虽然智能指针在许多情况下都非常有用,但它们并不能解决所有内存管理问题,例如,它们无法解决循环引用的问题(除非结合使用weak_ptr)。所以,理解并妥善使用智能指针依然非常重要。
#include < memory>,这个头文件在 C++ 中包含了许多关于内存管理的工具,包括智能指针如 std::unique_ptr 和 std::shared_ptr。
回忆一下,什么是内存泄露?
内存泄漏是指程序中已经动态分配的堆内存由于某种原因程序未释放或者无法释放,导致系统内存的浪费,降低系统性能的现象。如果程序持续运行,内存泄漏会导致系统可用内存逐渐下降,最终可能因为没有足够的内存可用而导致系统崩溃。
例如,以下是一个在 C++ 中产生内存泄漏的简单例子:
#include <iostream>int main() {while (true) {int* p = new int[100]; // 分配内存但未释放}return 0;
}
在这个例子中,程序在一个无限循环中不断地分配内存,但从未释放它。因此,它会不断消耗系统内存,最终可能导致系统崩溃。
解决内存泄漏的方法通常涉及找出程序中没有正确释放的内存,然后修改代码以确保内存在使用后被正确释放。在一些现代编程语言中,如 Java、Python、C#、Go 等,提供了垃圾收集机制,能够自动回收不再使用的内存,从而避免内存泄漏。在 C++ 中,使用智能指针(如 std::unique_ptr 或 std::shared_ptr)也可以帮助管理内存并避免内存泄漏。
二、普通指针和智能指针的比较案例
一个原始指针代码如下:
#include <iostream>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};int main() {MyClass* p = new MyClass(); // 创建 MyClass 实例// 必须显式调用 delete 来释放内存delete p;return 0;
}
在上述代码中,你需要手动管理内存。你必须显式调用 delete 来释放 p 所指向的内存。如果你忘记了,那就会导致内存泄漏。
智能指针:
#include <iostream>
#include <memory>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};int main() {std::unique_ptr<MyClass> p(new MyClass()); // 创建 MyClass 实例// 不需要显式调用 delete 来释放内存// 当 p 离开作用域时,它的析构函数会自动被调用,释放内存return 0;
}
std::unique_ptr:这是一个 std::unique_ptr 模板类的实例,其中的 MyClass 是模板参数,指定了这个智能指针可以指向 MyClass 类型的对象。
p:这是我们为智能指针变量取的名字。
new MyClass():这是使用 new 关键字创建一个新的 MyClass 对象的实例,并返回其内存地址。
std::unique_ptr p(new MyClass());:整个这句代码就是创建一个名为 p 的 std::unique_ptr 对象,它将管理我们新创建的 MyClass 对象的内存。
这里的p()括号里是初始化的意思、std::unique_ptr p(new MyClass()); 中的 new MyClass() 是用来初始化 std::unique_ptr 的。这个表达式创建了一个 MyClass 类型的对象,并返回了指向这个新创建对象的指针。然后,这个指针被用来初始化 std::unique_ptr。
最后结果:
接下来介绍那几种关键的智能指针
三、std::shared_ptr
下面给出一种用法:
#include <iostream>
#include <memory>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};void observe(std::shared_ptr<MyClass> p) {std::cout << "Inside observe function: shared_ptr use_count: " << p.use_count() << "\n";
}int main() {// 使用 std::make_shared 创建 shared_ptrstd::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();std::cout << "After ptr1 creation: shared_ptr use_count: " << ptr1.use_count() << "\n";{// 创建一个新的 shared_ptr,共享 MyClass 对象的所有权std::shared_ptr<MyClass> ptr2 = ptr1;std::cout << "After ptr2 creation: shared_ptr use_count: " << ptr1.use_count() << "\n";observe(ptr2);} // ptr2 离开作用域,MyClass 对象的所有权计数减一std::cout << "After ptr2 destruction: shared_ptr use_count: " << ptr1.use_count() << "\n";// 现在只有 ptr1 拥有 MyClass 对象
} // ptr1 离开作用域,MyClass 对象的所有权计数归零,MyClass 对象被删除
这段代码的主要目标是展示 std::shared_ptr 的用法和行为。std::shared_ptr 是一个智能指针,它允许多个指针共享同一个对象的所有权。
下面是对每一部分的详细解释:
首先定义了一个名为 MyClass 的结构体,它有一个构造函数和一个析构函数。构造函数和析构函数都会在被调用时输出一段信息。
observe 是一个函数,它接受一个 std::shared_ptr 作为参数,并输出该指针的 use_count(),即该指针当前的引用计数(共享所有权的指针数量)。
main 函数是这段代码的主要部分,展示了 std::shared_ptr 的使用:
使用 std::make_shared 创建了一个新的 std::shared_ptr,名为 ptr1,并使其指向一个新的 MyClass 实例。然后输出 ptr1 的 use_count(),此时应为 1,因为只有 ptr1 指向这个 MyClass 实例。
在一个新的作用域内(由 {} 定义),创建了一个新的 std::shared_ptr,名为 ptr2,并使其也指向 ptr1 所指向的 MyClass 实例。然后输出 ptr1 的 use_count(),此时应为 2,因为 ptr1 和 ptr2 都指向这个 MyClass 实例。
在同一作用域内,调用 observe 函数,并将 ptr2 作为参数传入。observe 函数会输出 ptr2 的 use_count(),此时也应为 2。
当这个作用域结束(即到达 }),ptr2 超出其作用域并被销毁。此时 MyClass 实例的引用计数减一。然后输出 ptr1 的 use_count(),此时应为 1,因为只剩下 ptr1 指向这个 MyClass 实例。
当 main 函数结束,ptr1 也超出其作用域并被销毁。此时 MyClass 实例的引用计数再次减一,变为 0,因此 MyClass 实例被删除,并调用其析构函数。
总的来说,这段代码展示了如何使用 std::shared_ptr 来共享一个对象的所有权,并在所有指向该对象的 std::shared_ptr 都被销毁时自动删除该对象。
最后:
四、std::unique_ptr
下面是一个例子:
#include <iostream>
#include <memory>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};void observe(std::unique_ptr<MyClass>& p) {if (p != nullptr) {std::cout << "Inside observe function: unique_ptr is not nullptr\n";} else {std::cout << "Inside observe function: unique_ptr is nullptr\n";}
}int main() {// 使用 std::make_unique 创建 unique_ptrstd::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();observe(ptr1);// 通过 std::move 将所有权从 ptr1 转移给 ptr2std::unique_ptr<MyClass> ptr2 = std::move(ptr1);observe(ptr1);observe(ptr2);
} // MyClass 对象被销毁,因为 ptr2 超出其作用域
在这段代码中,首先使用 std::make_unique 创建了一个 std::unique_ptr,名为 ptr1,并使其指向一个新的 MyClass 实例。然后调用 observe 函数来检查 ptr1 是否为 nullptr。
接下来,通过 std::move 将所有权从 ptr1 转移给新的 std::unique_ptr,名为 ptr2。此时 ptr1 变为 nullptr,因为所有权已经转移走了。
再次调用 observe 函数来检查 ptr1 和 ptr2。此时,ptr1 应为 nullptr,而 ptr2 不应为 nullptr。
当 main 函数结束时,ptr2 也超出其作用域并被销毁。此时,MyClass 实例也被删除,并调用其析构函数。这是因为 std::unique_ptr 在超出作用域时会自动删除其所指向的对象。
五、std::weak_ptr
std::weak_ptr 是一个用于解决 std::shared_ptr 循环引用问题的智能指针。std::weak_ptr 对于对象的所有权是无影响的,只是提供了一种访问共享对象的方式,不会增加引用计数。
以下是一个 std::weak_ptr 的使用例子:
#include <iostream>
#include <memory>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};void observe(std::weak_ptr<MyClass> p) {if (auto spt = p.lock()) { // Must be copied into a shared_ptr before usagestd::cout << "Inside observe function: weak_ptr is not expired, shared_ptr use_count: " << spt.use_count() << "\n";} else {std::cout << "Inside observe function: weak_ptr is expired\n";}
}int main() {std::weak_ptr<MyClass> wptr;{// 使用 std::make_shared 创建 shared_ptrstd::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();wptr = ptr;observe(wptr);} // ptr 离开作用域,MyClass 对象被销毁observe(wptr); // wptr 已经过期,因为 MyClass 对象已经被销毁
}
在这段代码中,首先定义了一个 std::weak_ptr,名为 wptr。
然后在一个新的作用域内(由 {} 定义),创建了一个 std::shared_ptr,名为 ptr,并使其指向一个新的 MyClass 实例。然后将 ptr 赋值给 wptr,使 wptr 也指向 ptr 所指向的 MyClass 实例。
接着调用 observe 函数,将 wptr 作为参数传入。在 observe 函数内部,通过调用 std::weak_ptr::lock 来获取一个指向 MyClass 实例的 std::shared_ptr,并输出其 use_count()。
当这个作用域结束(即到达 }),ptr 超出其作用域并被销毁,MyClass 实例的引用计数减一,变为 0,因此 MyClass 实例被删除,并调用其析构函数。
最后,再次调用 observe 函数,将 wptr 作为参数传入。此时 wptr 已经过期,因为 MyClass 实例已经被销毁,因此 std::weak_ptr::lock 会返回一个空的 std::shared_ptr。
六、std::shared_ptr |std::unique_ptr | std::weak_ptr三大智能指针的区别
std::shared_ptr, std::unique_ptr 和 std::weak_ptr 都是 C++11 引入的智能指针,它们的主要目标是提供自动化的内存管理,以避免内存泄漏。然而,它们之间的区别主要在于如何管理内存以及所有权语义:
std::shared_ptr:这是一种引用计数的智能指针。当你将一个对象的指针赋值给一个 std::shared_ptr 时,它就会开始进行引用计数。如果有其他 std::shared_ptr 指向同一对象,引用计数就会增加。当引用计数变为0(即没有 std::shared_ptr 指向该对象时),对象就会被自动删除。
std::unique_ptr:这种智能指针保证了对象的唯一所有权语义。在任何时候,只有一个 std::unique_ptr 可以指向一个对象。当 std::unique_ptr 超出作用域或被重新赋值时,其原来指向的对象就会被自动删除。如果你需要将所有权转移给另一个 std::unique_ptr,你可以使用 std::move。
std::weak_ptr:这是一种特殊的智能指针,主要用来解决 std::shared_ptr 的循环引用问题。std::weak_ptr 本身不对对象的生命周期造成影响,也就是说,拥有一个指向对象的 std::weak_ptr 不会阻止对象被删除。它只是为了提供一种访问已经存在(并且被 std::shared_ptr 管理)的对象的方式,而不会增加引用计数。要使用 std::weak_ptr,你需要先将其升级为 std::shared_ptr,如果此时对象仍然存在,升级操作就会成功。
这些智能指针都是 C++ 标准库的一部分,设计用来自动管理内存,以避免常规指针可能导致的内存泄漏和其他问题。在编写 C++ 程序时,应优先考虑使用这些智能指针,而不是裸指针。
小插曲:
今天用vscode的时候 调试控制台不输出信息了 比如我print你好,他竟然不输出,只输出几个线程的信息,高的很是烦人,后面找到了解决方案:解决方法
相关文章:

【C++】智能指针 学习总结 |std::shared_ptr |std::unique_ptr | std::weak_ptr
文章目录 前言一、智能指针介绍二、普通指针和智能指针的比较案例三、std::shared_ptr四、std::unique_ptr五、std::weak_ptr六、std::shared_ptr |std::unique_ptr | std::weak_ptr三大智能指针的区别 前言 参考答案:chatgpt 一、智能指针介绍 智能指针是C的一种…...
iptables防火墙
文章目录 一.linux防火墙基础1.linux 包过滤防火墙概述1.1netfilter1.2 iptables 2.包过滤的工作层次2.1 通信的五元素和四元素 3.iptables 的表、链结构3.1 规则链3.2 默认包括5种规则链3.3 规则表3.4 默认包括4个规则表 二.数据包过滤的匹配流程1.规则表之间的顺序2.规则链之…...

properties、yaml作为配置文件的特点
说明:在软件开发中,经常需要把一些配置写在文件中,如数据库配置、MyBatis配置等。这样,后续如果数据库参数有改动,就可以避免直接对代码做修改,只要修改配置文件中关于数据库的配置。关于配置文件的选择&am…...

JavaSE-03 【流程控制语句】
文章目录 JavaSE-03 【流程控制语句】第一章 流程控制1.1 流程概述1.2 顺序结构 第二章 判断语句2.1 判断语句---if2.2 判断语句---if...else2.3 判断语句---if...else if ... else 第三章 选择语句3.1 选择语句--switch3.2 case的穿透性 第四章 循环语句4.1 循环概述4.2 循环语…...

笔记本电脑的BIOS是怎么保护安全的?
随着攻防技术的不断演进,像BIOS攻击、高级网络代码攻击等手段层出不穷,“受害者”也不仅限于企业级服务器、存储,很多魔爪也开始伸向了拥有商业机密数据的PC。 BIOS是Basic Input/Output System(基本输入/输出系统)的…...

Xubuntu之将rm删除内容移至回收站(一百七十七)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…...

STM32F407实现1588v2(ptpd)
硬件: STM32F407ZGT6开发板 软件: VSCode arm-none-eabi-gcc openOCD st-link 在github搜到一个在NUCLEO-F429ZI开发板上移植ptpd的example,因为和F407差别很小,所以就打算用这个demo移植到手头的开发板上。因为目前只需要…...
架构师如何找到自己的商业模式
作为一个架构师,必须要在有限的资源下最大化架构活动所带来的商业价值。对于任何一个架构活动而言,架构师的可用资源,包括商业成本、研发成本、时间成本、迁移成本等等,都是非常有限的。但架构活动就是要在这些限制条件之下&#…...

SQLServer2022安装(Windows),已验证
一、SQLServer2022下载 1、官网下载地址 SQL Server 下载 | Microsoft 2、下载安装包 2.1、选择Developer版本,立即下载。 2.2、打开下载文件夹,双击运行SQL2022-SSEI-Dev.exe 尝试运行SQL2022-SSEI-Dev.exe,会收到以下信息:“…...

facenet, dlib人脸识别,人体检测,云数据库mysql,QQ邮箱,手机验证码,语音播报
目录 部分代码展示: 录入部分 识别部分编辑 活体检测部分编辑 同步到云数据库MySQL 其他操作 部分图片展示: 完整代码加ui链接: 涉及到的一些知识点的文章 部分代码展示: 录入部分 识别部分 活体检测部分 同步到云数…...
Spring Boot 面试题——定时任务
目录 1.什么是 cron 表达式?如何使用?2.Scheduled 注解有什么作用?3.介绍一下 spring-boot-starter-quartz。4.在 Spring Boot 中如何实现定时任务?4.1.使用 Schedule 注解4.2.Quartz 框架 1.什么是 cron 表达式?如何使…...

总建面64万平,配3所幼儿园+54班九年制学校,坪山江岭竹元规划
近日,坪山区城市更新和土地整备局发布,关于《坪山区马峦街道江岭竹元片区城市更新单元规划》已通过深圳市城市规划委员会法定图则委员会审批。现予以公告。 项目位于坪山区马峦街道,南邻南坪快速路,北邻比亚迪路,东西两…...

python3 爬虫相关学习7:使用 BeautifulSoup下载网页图片到本地文件夹
目录 1 一个爬图片pic的代码的例子 1.1 学习的原文章 1.2 原始代码的问题总结 问题1 问题2 问题3 其他问题 1.3 原始代码 2 直接在cmd里 python运行报错 和 处理 2.1 运行报错 2.2 报错原因: 没有提前安装这个bs4 模块 2.3 如何提前知道我的python环境…...

windows平台python脚本执行环境搭建笔记
1.python脚本环境下载 这里是原始发布源: https://www.python.org/downloads/release/python-3114/https://www.python.org/downloads/release/python-3114/安装时记得添加进系统path,这样你可以随时调用python环境。 2.扩展模块的安装 step1.找到py…...
MyBatis的动态SQL之OGNL(Object-Graph Navigation Language)表达式以及各种标签的用法
MyBatis的动态SQL 1、if标签的用法2、choose标签的用法3、where标签4、set标签5、trim的用法6、foreach标签7、bind标签 使用过JDBC或者是其他的ORM框架的开发者都知道,在很多操作中都需要去根据具体的条件进行SQL语句的拼接,并且在有些时候一些标点符号…...

基于Java+Springboot+Vue的二次元商城网站设计与实现
博主介绍:✌擅长Java、微信小程序、Python、Android等,专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇🏻 不然下次找不到哟 Java项目精品实战案…...

MyBatis操作数据库实现
说明:MyBatis是作用于三层架构开发,数据访问层(Data Access Object)的框架,用于访问数据库,对数据进行操作。 一、环境搭建 首先,创建一个SpringBoot模块,然后把MyBatis的环境搭建…...

Git GitLab 使用及规范
Git 基本操作 Git安装配置及基本使用 从官网下载安装包,手动完成安装。打开Git Bash命令行工具,执行命令ssh-keygen -t rsa -C Email-Addresss生成一个密钥对。登录到GitLab,点击右上角你的用户头像,点击Edit Profile settings&…...

【SpringCloud——Sentinel】
一、什么是雪崩? 微服务调用链路中的某个服务发生故障,引起整个链路中的所有微服务都不可用,这就是雪崩。 二、解决雪崩问题的常见措施 1、超时处理 设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休…...

面试专题:计算机网络常见面试点总结
socket、tcp、udp、http 的认识及区别 socket、tcp、udp、http 的认识及区别 一、先来一个讲TCP、UDP和HTTP关系的 1、TCP/IP是个协议组,可分为三个层次:网络层、传输层和应用层。 在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。 在传…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...

ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...