定位new运算符

通常,new 从堆中分配内存,但它还有另一种称为 定位(placement)new 运算符,

它可以让我们指定要使用的位置。可以通过这个特性来设置内存管理规程,处理需要

通过特定地址进行访问的硬件或在特定位置创建对象。

要使用定位 new 特性,需要包含头文件 new。使用定位 new 运算符时,变量后面可

以有方括号,也可以没有。下面代码中同时使用了传统的 new 运算 符和定位 new 运算符:

// newplace.cpp -- using placement new
#include <iostream>
#include <new> // 使用定位 new 所需要的头文件
const int BUF = 512;
const int N = 5;
char buffer[BUF];      // 数组缓冲区
int main()
{
    using namespace std;

    double *pd1, *pd2;
    int i;
    cout << "Calling new and placement new:\n";
    pd1 = new double[N];           // 从堆中分配
    pd2 = new (buffer) double[N];  // 使用缓冲数组
    for (i = 0; i < N; i++)
        pd2[i] = pd1[i] = 1000 + 20.0 * i;
    cout << "Memory addresses:\n" << "  heap: " << pd1
        << "  static: " <<  (void *) buffer  <<endl;
    cout << "Memory contents:\n";
    for (i = 0; i < N; i++)
    {
        cout << pd1[i] << " at " << &pd1[i] << "; ";
        cout << pd2[i] << " at " << &pd2[i] << endl;
    }

    cout << "\nCalling new and placement new a second time:\n";
    double *pd3, *pd4;
    pd3= new double[N];            // 从堆中分配一块新的内存
    pd4 = new (buffer) double[N];  // 覆盖了原来的数据
    for (i = 0; i < N; i++)
        pd4[i] = pd3[i] = 1000 + 40.0 * i;
    cout << "Memory contents:\n";
    for (i = 0; i < N; i++)
    {
        cout << pd3[i] << " at " << &pd3[i] << "; ";
        cout << pd4[i] << " at " << &pd4[i] << endl;
    }

    cout << "\nCalling new and placement new a third time:\n";
    delete [] pd1;
    pd1= new double[N];
    pd2 = new (buffer + N * sizeof(double)) double[N];
    for (i = 0; i < N; i++)
        pd2[i] = pd1[i] = 1000 + 60.0 * i;
    cout << "Memory contents:\n";
    for (i = 0; i < N; i++)
    {
        cout << pd1[i] << " at " << &pd1[i] << "; ";
        cout << pd2[i] << " at " << &pd2[i] << endl;
    }
    delete [] pd1;
    delete [] pd3;
    // cin.get();
    return 0;
}

运行输出:

Calling new and placement new:
Memory addresses:
  heap: 001D7E98  static: 0037A138
Memory contents:
1000 at 001D7E98; 1000 at 0037A138
1020 at 001D7EA0; 1020 at 0037A140
1040 at 001D7EA8; 1040 at 0037A148
1060 at 001D7EB0; 1060 at 0037A150
1080 at 001D7EB8; 1080 at 0037A158

Calling new and placement new a second time:
Memory contents:
1000 at 001D8840; 1000 at 0037A138
1040 at 001D8848; 1040 at 0037A140
1080 at 001D8850; 1080 at 0037A148
1120 at 001D8858; 1120 at 0037A150
1160 at 001D8860; 1160 at 0037A158

Calling new and placement new a third time:
Memory contents:
1000 at 001D7E98; 1000 at 0037A160
1060 at 001D7EA0; 1060 at 0037A168
1120 at 001D7EA8; 1120 at 0037A170
1180 at 001D7EB0; 1180 at 0037A178
1240 at 001D7EB8; 1240 at 0037A180

定位new 的一个主要特征就是在先分配好的缓冲区中划分出一部分用作我们自己的内存规划。如同上面,先分配好一段共 512 字节大小的 buffer 缓冲区,然后将 5*double (40个字节)大小的区域用来存放 double 数组。这个 buffer 缓冲区域是由全局数组变量 buffer[512] 定义出来的,这块区域位于静态数据区中,当然这不是必须的,这块缓冲区域同样可以位于栈中,堆中都没问题,关键是它是事先被划分好的,然后我们再在里边划分出一块为己用,这就达到了”定位“的目的。

需要注意 定位new 的使用语法, new (buffer) double[N]; 表示将拥有 N 个 double 元素的数组放在 buffer 缓冲区中,并且是从开始存放,这也是上面程序中第 1 种情况所演示的。在第 2 种情况里,再次存放了另外一个数组,也是从开始处存放,这会覆盖原来的数据。第 3 种情况,并不是从开始处存放的,而是从一个偏移处开始存放,这样可以避免覆盖掉之前的数据。

定位new 应用于对象

上面的定位 new 运算符应用于内置的数据类型(如 double),它也可以应用于对象,此时情况有些不同:

#include <iostream>
#include <string>
#include <new>
using namespace std;
const int BUF = 512;

class JustTesting
{
private:
    string words;
    int number;
public:
    JustTesting(const string & s = "Just Testing", int n = 0)
    {words = s; number = n; cout << words << " constructed\n"; }
    ~JustTesting() { cout << words << " destroyed\n";}
    void Show() const { cout << words << ", " << number << endl;}
};
int main()
{
    char * buffer = new char[BUF];       // 分配一块缓冲区

    JustTesting *pc1, *pc2;

    pc1 = new (buffer) JustTesting;      // 将对象放入 buffer[] 中
    pc2 = new JustTesting("Heap1", 20);  // 将对象放到堆分配的内存中

    cout << "Memory block addresses:\n" << "buffer: "
        << (void *) buffer << "    heap: " << pc2 <<endl;
    cout << "Memory contents:\n";
    cout << pc1 << ": ";
    pc1->Show();
    cout << pc2 << ": ";
    pc2->Show();

    delete pc1;

    JustTesting *pc3, *pc4;
    pc3 = new (buffer) JustTesting("Bad Idea", 6);  //之前的被覆盖了
    pc4 = new JustTesting("Heap2", 10);
    cout << "Memory contents:\n";
    cout << pc3 << ": ";
    pc3->Show();
    cout << pc4 << ": ";
    pc4->Show();

    delete pc2;                          // 释放 Heap1
    delete pc4;                          // 释放 Heap2
    delete [] buffer;                    // 释放 buffer
    cout << "Done\n";
    // std::cin.get();
    return 0;
}

运行输出:

Just Testing constructed
Heap1 constructed
Memory block addresses:
buffer: 004D8800    heap: 00658EF0
Memory contents:
004D8800: Just Testing, 0
00658EF0: Heap1, 20
Bad Idea constructed
Heap2 constructed
Memory contents:
004D8800: Bad Idea, 6
004D8AA0: Heap2, 10
Heap1 destroyed
Heap2 destroyed
Done

上面程序中,定位 new 运算符创建的第 2 个对象覆盖了第 1 个对象的内存单元。因此必须自己管理好缓冲区的内存单元分配,将不同的对象放在缓冲区的不同地址上,以确保两个内存单元不重叠,如:

pc1 = new (buffer) JustTesting;

pc3 = new (buffer + sizeof (JustTesting)) JustTesting("Better Idea", 6);

另外,delete 删除 pc2 和 pc4 时,自动调用了 pc2 和 pc4 指向对象的析构函数;然而,在将 delete [] 用于 buffer 时,确实释放了由 new 分配的数组内存块,但这并不会为定位 new 运算符创建的对象调用析构函数。这个可以从函数中的输出可以看到,析构函数只宣布了 "Heap1" 和 "Heap2" 的死亡,却没有宣布 "Just Testing" 和 "Bad Idea" 的死亡。

解决上述没有调用析构函数问题的方法是,显示为定位 new 运算符创建的对象调用析构函数,这也是显示调用析构函数的少数几种情形之一。显示调用析构函数,必须指定要销毁的对象。由于有指向对象的指针,因此可以如下调用析构函数:

pc3->~JustTesting();
pc1->~JustTesting();

加入合适的delete和显式的析构函数调用,能够解决这样的问题,但是需要注意的一点是正确的删除顺序。对于使用定位new运算符创建的对象,应以与创建顺序相反的顺序进行删除。原因在于,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于存储这些对象的缓冲区。

#include <iostream>
#include <string>
#include <new>
using namespace std;
const int BUF = 512;

class JustTesting
{
private:
    string words;
    int number;
public:
    JustTesting(const string & s = "Just Testing", int n = 0)
    {words = s; number = n; cout << words << " constructed\n"; }
    ~JustTesting() { cout << words << " destroyed\n";}
    void Show() const { cout << words << ", " << number << endl;}
};
int main()
{
   // char * buffer = new char[BUF];       // 分配一块缓冲区
       char buffer[BUF];

    JustTesting *pc1, *pc2;

    pc1 = new (buffer) JustTesting;      // 将对象放入 buffer[] 中
    pc2 = new JustTesting("Heap1", 20);  // 将对象放到堆分配的内存中

    cout << "Memory block addresses:\n" << "buffer: "
        << (void *) buffer << "    heap: " << pc2 <<endl;
    cout << "Memory contents:\n";
    cout << pc1 << ": ";
    pc1->Show();
    cout << pc2 << ": ";
    pc2->Show();

    JustTesting *pc3, *pc4;

// 修正重叠问题
    pc3 = new (buffer + sizeof (JustTesting))
                JustTesting("Better Idea", 6);
    pc4 = new JustTesting("Heap2", 10);

    cout << "Memory contents:\n";
    cout << pc3 << ": ";
    pc3->Show();
    cout << pc4 << ": ";
    pc4->Show();

    delete pc2;           // 释放 Heap1
    delete pc4;           // 释放 Heap2

// 显示销毁由定位 new 创建的对象,注意调用的顺序
    pc3->~JustTesting();  // 销毁由 pc3 指向的对象
    pc1->~JustTesting();  //  销毁由 pc1 指向的对象
    //delete [] buffer;     // 释放 buffer

    return 0;
}

还需要注意的是,调用析构函数的正确顺序。对于使用定位 new 运算符创建的对象,应该和创建的顺序相反。因为,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于存储这些对象的缓冲区。

时间: 2024-10-19 20:11:51

定位new运算符的相关文章

定位new函数的使用

定位new函数的使用 对于new运算符还有一种变体,就算定位new运算,这种运算符可以指定使用的位置,可以用来把信息存放在指定的硬件地址上. #include <new> ... p = new (address) type ; p2 = new (address + N*sizeof(type) ) type [M]; 定位new运算符的原理是返回传递给它的地址,并强制转换成void * 如果address位于静态内存而不是堆内存中,就不能用delete进行释放. 原文地址:https://

C++学习笔记(3)

本学习笔记是C++ primer plus(第六版)学习笔记.是C++学习笔记(2)的后续.复习C++基础知识的可以瞄瞄. 转载请注明出处http://www.cnblogs.com/zrtqsk/p/3881141.html,谢谢!如下. 第九章 1.C++程序的组成—— (1).头文件: 包含结构声明和使用这些结构的原型. (2).源代码文件: 包含与结构有关的函数的代码. (3).源代码文件: 包含调用与结构有关的函数的代码. 2.头文件—— (1).常包含的内容: 函数原型:#defin

C++ primer plus读书笔记——第9章 内存模型和名称空间

第9章 内存模型和名称空间 1. 头文件常包含的内容: 函数原型. 使用#define或const定义的符号常量. 结构声明. 类声明. 模板声明. 内联函数. 2. 如果文件名被包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找.但如果头文件名包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录(或其他目录,这取决于编译器).如果没有在那里找到头文件,则将在标准位置中查找.因此在包含自己的头文件时,应使用引号而不是尖括号. 3. 链接程序将目标文件代码.库代码和

Google C++ 风格指南内容整理

之前一直没有全面的看过Google C++风格指南,现在很多公司进行C++开发都要求按照Google C++风格.在这个网站 http://zh-google-styleguide.readthedocs.org/en/latest/contents/  有人已经把其翻译成中文.为了便于以后查看,下面的内容完全是来自于这个网站,只是把多个网页的内容整理放在了一起. 1.      头文件: 通常每一个.cc文件都有一个对应的.h文件.也有一些常见例外,如单元测试代码和只包含main()函数的.c

C++ primer plus读书笔记——第12章 类和动态内存分配

第12章 类和动态内存分配 1. 静态数据成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域运算符来指出静态成员所属的类.但如果静态成员是整形或枚举型const,则可以在类声明中初始化. P426-P427类静态成员的声明和初始化 //strnbad.h class StringBad { private: static int num_strings; … }; //strnbad.cpp int StringBad::num_strings = 0; 不能在类声明中初始化静态

GoogleCpp风格指南 3)类

3 类 Classes 类是C++中代码的基本单元; 显然, 它们被广泛使用; 本节列举了写一个类时的主要注意事项; 3.1 构造函数的职责 Doing Work in Constructors Tip 构造函数中只进行那些没什么意义的(trivial 译注: 简单初始化对于程序执行没有实际的逻辑意义, 因为成员变量"有意义"的值大多不再构造函数中确定)初始化, 可能的话, 使用 Init()方法集中初始化有意义的(non-trival)数据; [Add] 在ctor中防止做复杂的初始

浅谈new与delete(2)

上一节介绍了new运算符的常规用法,这一节我们一起来看看定位new运算符的用法. 1.定位new运算符是在已分配的内存空间进行二次分配.例如: char *buffer = new char[512]; Point *p = new (buffer) Point(); //Point为类名 Point *q = new (buffer + sizeof(Point)) Point(); 即:在内存缓冲区buffe中创建对象p.q 2.delete与常规new运算符配合使用,但不能与定位new运算

谈new

1. New在c++里是用来在堆里申请一段内存.程序结束之前需要用delete手动释放内存.并且不受作用域的控制. 格式: typename * pointer_name = new typename; -- delete  pointer_name; 如果为数组申请内存,因为数组是存储在连续的一段内存里,所以, 格式: typename * pointer_name = new typename [num_elements]; -- delete  [] pointer_name; 在申请内存

内存的特性 运算符和表达式

内存指的是内部存储器,内存条只是内部存储器中的一种 内存的作用:用于存储正在执行的程序与数据 内存的定位:内存是CPU与其他外部设备之间交换数据的桥梁 内存的特性:RAM(randoom access memory)随机存取存储器,可读可写,一旦断电后数据全部丢失 ************************************************************************** 计算机中,程序的核心工作就是处理数据,处理数据的过程中就是将数据按照程序所设定的规则