C++的核心理念之一是RAII,Resource Acquisition Is Initialization,资源获取即初始化。资源有很多种,内存、互斥锁、文件、套接字等;RAII可以用来实现一种与作用域绑定的资源管理方法(如std::lock_guard);这些都不在本文的讨论范围之内。

内存是一种资源。从字面上来看,“资源获取”是指在栈或堆上开辟空间,“初始化”是指调用构造函数,“即”的意思是两者是绑定起来的。对应地,资源销毁即释放。这种机制保证了任何函数访问参数对象时不会访问到非法地址,除了构造和析构函数以外的任何函数的参数都不会是未初始化的。

然而,C++作为能够面向底层的语言,允许我们把内存获取与初始化分开来:std::malloc和std::free用于分配和释放内存,定位new表达式和析构函数的显式调用用于初始化和销毁对象。

malloc与free

std::malloc用于分配内存,std::free用于释放内存,两者都定义在<cstdlib>中:

void* malloc(std::size_t size);
void free(void* ptr);

比如,我想要分配一个int的动态内存,就应该给size填上sizeof(int),返回值就是指向那个int的指针。如果是数组,就给size乘上元素个数。注意在C++中,从void*到T*的转换不能是隐式的。

要回收它,把指针传给free,不需要size:

#include <iostream>
#include <cstdlib> int main()
{
auto p = static_cast<int*>(std::malloc(sizeof(int) * 10));
p[0] = 1;
p[1] = 2;
std::cout << p[0] << ' ' << p[1] << std::endl;
std::free(p);
}

如果std::malloc过程中发生了错误,比如内存不足,std::malloc会返回NULL,nullptr的前身。实际使用时都应该考虑这种情况。

std::malloc得到的内存必须用free释放。std::malloc/std::free的内存分配与new/delete不互通。

定位new表达式

std::malloc分配的内存是未经初始化的,对于int等内置类型可以直接使用,而类类型则未必,需要先初始化才能使用。

我们知道,类的非静态成员函数都有一个隐式的this指针作为参数,构造函数也不例外,在未初始化的内存上构造对象,就是把这块内存的指针作为this传入构造函数。不幸的是,没有显式调用构造函数这种语法:

auto ptr = static_cast<A*>(std::malloc(sizeof(A)));
ptr->A("hello"); // error

(在MSVC中,ptr->A::A("hello");是合法的,但这是非标准的。)

我们需要定位new,它定义在<new>中,需要#include以后才能使用:

void* operator new(std::size_t, void*);

new运算符是可以重载的,new运算符的功能是为new表达式中的构造函数提供this指针。但是定位new不行,它总是忠实地返回它的第二个参数。

定位new表达式有以下形式:

new (ptr) Type;
new (ptr) Type(args);
new (ptr) Type[size];
new (ptr) Type[size]{list};

ptr为要当作this的指针,Type为要构造的类型,args为可能为空的参数列表,size为数组大小(可以动态),list为数组元素列表。

利用定位new,把ptr作为this的构造函数可以这样调用:

#include <iostream>
#include <cstdlib>
#include <string>
#include <utility> class A
{
public:
A(std::string s) : string(std::move(s))
{
std::cout << "A::A(std::string)" << std::endl;
}
std::string& get()
{
return string;
}
private:
std::string string;
}; int main()
{
auto ptr = static_cast<A*>(std::malloc(sizeof(A)));
// std::cout << ptr->get() << std::endl; // disaster
// ptr->A("hello"); // error
new (ptr) A("hello");
std::cout << ptr->get() << std::endl;
// delete ptr; // disaster
// what's next?
}

不要因为ptr简单就不加括号,括号不是为了什么运算符优先级,而是定位new表达式的一部分。

刚才不是说std::malloc与new不互通吗?怎么在std::malloc来的ptr上用定位new了呢?因为定位new根本不插手内存分配,和std::malloc是两回事。

定位new不止可以用于std::malloc来的动态内存,甚至可以是局部变量:

char buffer[sizeof(A)];
auto ptr = new (buffer) A("hello");
std::cout << ptr->get() << std::endl;

与常规的new一样,定位new表达式也返回一个指针,就是Type*类型的ptr。

显式调用析构函数

上面通过std::malloc得到的ptr,为什么不能直接std::free呢?因为std::string里面的资源还没释放,正确的做法是调用A的析构函数。

不能对一个对象调用构造函数,那么析构函数呢?答案是肯定的。只是~在形式上有点怪,尤其是当你把模板参数T换成int后得到ptr->~int()时,后者直接写是不合法的。

所以对于上面的ptr,要这样才能释放所有资源:

ptr->~A();
std::free(ptr);

显式调用析构函数,就像在帮编译器做事情一样。编译器会不会领情呢?写个代码测试一下吧:

#include <iostream>
#include <string>
#include <utility> class A
{
public:
A(std::string s) : string(std::move(s))
{
std::cout << "A::A(std::string)" << std::endl;
}
std::string& get()
{
std::cout << "A::get()" << std::endl;
return string;
}
~A()
{
std::cout << "A::~A()" << std::endl;
}
private:
std::string string;
}; int main()
{
{
A a("");
a.~A();
}
std::cout << "I'm OK" << std::endl;
}

程序输出:

A::A(std::string)
A::~A()
A::~A()
I'm OK

看来编译器并不领情,即使我们调用了析构函数,变量离开作用域时还会再调用一次。尽管在MSVC和GCC中,I'm OK都成功输出了,std::string都挺住了错误的析构,但是这个程序的行为仍然是未定义的。

因此,定位new语句与析构函数的显式调用必须配对。

定位new表达式与显式调用析构函数的更多相关文章

  1. C++不能显式调用构造函数,会生成匿名对象,这点与Java完全不一样!

    Java可以直接调用同名构造函数,仅仅起初始化的功能,并不构造新的对象,但是C++里面没有.看一下这段代码: class A { public: A() { printf("A() \n&q ...

  2. linux下动态链接库(.so)的显式调用和隐式调用

    进入主题前,先看看两点预备知识. 一.显式调用和隐式调用的区别 我们知道,动态库相比静态库的区别是:静态库是编译时就加载到可执行文件中的,而动态库是在程序运行时完成加载的,所以使用动态库的程序的体积要 ...

  3. C++构造函数详解及显式调用构造函数

    来源:http://www.cnblogs.com/xkfz007/archive/2012/05/11/2496447.html       c++类的构造函数详解                  ...

  4. C++中构造函数详解及显式调用构造函数

    C++构造函数详解及显式调用构造函数                                         c++类的构造函数详解                        一. 构造函 ...

  5. C++如何显式调用常成员函数

    C++的常成员函数与同名成员函数重载时,该如何显式调用常成员函数? 具体的一个小例子: #include <iostream> using namespace std; class C1 ...

  6. 循环引擎 greenlet 没有显式调度的微线程,换言之 协程

    小结: 1. micro-thread with no implicit scheduling; coroutines, in other words. 没有显式调度的微线程,换言之 协程 2. 一个 ...

  7. based on Greenlets (via Eventlet and Gevent) fork 孙子worker 比较 gevent不是异步 协程原理 占位符 placeholder (Future, Promise, Deferred) 循环引擎 greenlet 没有显式调度的微线程,换言之 协程

    gevent GitHub - gevent/gevent: Coroutine-based concurrency library for Python https://github.com/gev ...

  8. simplest_dll 最简dll的创建与隐式调用(显式调用太麻烦,个人不建议使用)

    首先需要有个头文件,名字随便写  假设test.h //test.h #ifndef _TEST_H #define _TEST_H #ifdef TEST_EXPORTS //通过宏定义控制是输入还 ...

  9. el表达式不显示值

    1.场景是自己搭建一个ssm的项目,登录页面跳转到首页,首页显示登录用户的信息,用request传递的值,用el表达式在jsp页面中没有显示 2.解决办法 早jsp的代码中添加头<%@ page ...

随机推荐

  1. python制作一键启动脚本

    我们的系统环境或许没有Python环境,又想使用脚本,这就要使该脚本能脱离Python环境独立运行,比如说将该脚本打包成exe可执行文件等.那么怎么做呢?你可能想到py2exe和pyinstaller ...

  2. [JVM教程与调优] JVM都有哪些参数类型?

    JDK本身是提供了一些监控工具,有一些是命令行,也有图形界面.本次介绍命令行如何进行监控. 命令行是非常重要的,因为在我们生产环境基本上是没有图形界面的,完全是通过命令行. 主要内容: JVM的参数类 ...

  3. 基于海龟编辑器python少儿编程

    Python 少儿教程 为什么要学习编程 扫地机器人.物流机器人.自动泊车系统.无人超市.3D打印.微信.支付宝等等,随着人工智能时代的到来,越来越多的岗位将被机器人所替代. 所以,学习编程的最终目的 ...

  4. 解决使用requests_html模块,html.render()下载chromium报错、速度慢问题

    来源:https://www.cnblogs.com/xiaoaiyiwan/p/10776493.html 稍作修改 1.第一步,代码如下: from requests_html import HT ...

  5. PyTorch专栏(二)

    专栏目录: 第一章:PyTorch之简介与下载 PyTorch简介 PyTorch环境搭建 第二章:PyTorch之60min入门 PyTorch 入门 PyTorch 自动微分 PyTorch 神经 ...

  6. 一个完整的机器学习项目在Python中的演练(二)

    大家往往会选择一本数据科学相关书籍或者完成一门在线课程来学习和掌握机器学习.但是,实际情况往往是,学完之后反而并不清楚这些技术怎样才能被用在实际的项目流程中.就像你的脑海中已经有了一块块"拼 ...

  7. python文件封装成*.exe

    python文件封装成*.exe文件(单文件和多文件) 环境:win10 64位 python3.7 原文: https://www.cnblogs.com/jackzz/p/9431923.html ...

  8. docker 本地镜像导入导出 compose安装

    docker 本地镜像导入导出 1.Docker导入本地gz镜像 [root@rocketmq-nameserver4 dev]# cat alibaba-rocketmq-3.2.6.tar.gz ...

  9. Django-on_delete

    一.外键的删除 关于on_delete的总结 1.常见的使用方式(设置为null) class BookModel(models.Model): """ 书籍表 &quo ...

  10. RabbitMQ集群架构(HA)并结合.NET Core实操

    一.前言 已经一年没有更新博客了,由于公司事务比较多,并且楼主我也积极在公司项目中不断实践.net core.DDD以及Abp vnext,也积累了一些吐血经验,目前我在做一家在线教育公司负责智慧校园 ...