类和动态内存分配(一)
1. 静态类成员函数
如果将成员函数声明为静态的(函数声明必须包含关键字static,但如果函数定义是独立的,则其中不能包含关键字static),则不能通过对象调用静态成员函数,且由于静态成员函数不能与特定的对象相关联,因此静态成员函数只能使用静态数据成员。
2. 在构造函数中使用new时应注意的事项
- 如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete。
- new和delete必须相互兼容,new对应delete,new[]对应delete[]。
- 如果有多个构造函数,则必须以相同的方式使用new,要么都带,要么都不带。(因为只有一个析构函数,所有构造函数必须与其兼容)
- 应定义一个复制构造函数,通过深度复制将一个对象初始化为另外一个对象。(具体地说,复制构造函数应分配足够的空间来存储复制的数据,并复制数据,而不仅仅是数据的地址,同时,若果有受影响的静态成员,要及时在复制构造函数中更新该静态成员)
- 应当定义一个赋值运算符,通过深度复制一个对象给另外一个对象。(同时需要检查自我赋值的情况,释放成员指针以前指向的内存,复制数据而不仅仅是数据的地址,并返回一个指向调用对象的引用)。
3. 有关函数返回对象的说明
当成员函数或独立的函数返回对象时,常用的方式有:
3.1 返回指向const对象的引用
使用const引用的主要原因是为了提高效率,但该方式存在一定的限制。如果函数返回(通过调用对象的方法或将对象作为参数)传递给它的对象,可以通过返回引用来提高效率。
3.2 返回指向非const对象的引用
两种常见的返回非const对象情形是,重载赋值运算符以及重载与cout一起使用的<<运算符。前者这样做是为了提高效率,而后者必须这么做。
3.3 返回对象
当被返回的对象是被调用函数中的局部变量,则应该返回对象。
3.4 返回从const对象
返回const对象可以避免类似force1 + force2 = net
这种奇异属性误用可能带来的错误。
总的来说,如果方法或函数要返回局部对象,则应该返回对象。如果方法或函数要返回一个没有公有复制构造函数的类(如ostream类)的对象,则必须返回一个指向这个对象的引用。如果方法或函数可以返回对象,也可以返回对象的引用,则优先选择引用,提高效率。
4. new与delete
定位new运算符能够让使用者在分配内存时能够指定内存位置。但这种运算符在应用于对象的时候,应该注意:delete可以与常规的new运算符配合使用,但不能与定位new运算符配合使用。原因见下例:
// placenew1.cpp -- new, placement new, no delete
#include <iostream>
#include <new>
#include <string>
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]; // get a block of memory
JustTesting *pc1, *pc2;
pc1 = new (buffer) JustTesting; // place object in buffer
pc2 = new JustTesting("Heap1", 20); // place object on heap
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) JustTesting("Bad Idea", 6);
pc4 = new JustTesting("Heap2", 10);
cout << "Memory contents:\n";
cout << pc3 << ": ";
pc3->Show();
cout << pc4 << ": ";
pc4->Show();
delete pc2; // free Heap1
delete pc4; // free Heap2
delete[] buffer; // free buffer
cout << "Done\n";
return 0;
}
其中,使用new运算符创建了一个512字节的内存缓存区,然后在使用new运算符在堆中创建两个JustTesting
对象。并试图使用定位new运算符在内存缓冲区创建两个JustTesting
对象,最后在使用delete来释放new分配的内存时出现异常,上述代码的输出如下:
Just Testing constructed
Heap1 constructed
Memory block addresses:
buffer: 00320AB0 heap: 00320CE0
Memory contents:
00320AB0: Just Testing, 0
00320CE0: Heap1, 20
Bad Idea constructed
Heap2 constructed
Memory contents:
00320AB0: Bad Idea, 6
00320EC8: Heap2, 10
Heap1 destroyed
Heap2 destroyed
Done
根据打印信息,很明显发现pc1
和pc3
的析构函数未被正常调用,且pc3
在创建的时候,直接覆盖了pc1
的内存。
在使用定位new运算符时,要注意一下两点:
- 要保证每个对象要使用不同的内存单元(即需要提供两个不同的内存地址,并确保两个内存单元不存在重叠)。
- 如果使用定位new运算符来为对象分配内存,必须保证其析构函数能够正常的被调用(delete可以和常规的new运算符配合使用,但不能与定位new运算符配合使用,因此,delete对于定位new运算符对其分配内存做了什么一无所知)。
修改后的代码:
// placenew2.cpp -- new, placement new, no delete
#include <iostream>
#include <new>
#include <string>
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]; // get a block of memory
JustTesting *pc1, *pc2;
pc1 = new (buffer) JustTesting; // place object in buffer
pc2 = new JustTesting("Heap1", 20); // place object on heap
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;
// fix placement new location
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; // free Heap1
delete pc4; // free Heap2
// 显式销毁放置的新对象
pc3->~JustTesting(); // destroy object pointed to by pc3
pc1->~JustTesting(); // destroy object pointed to by pc1
delete[] buffer; // free buffer
cout << "Done\n";
return 0;
}
其对应的输出:
Just Testing constructed
Heap1 constructed
Memory block addresses:
buffer: 00320AB0 heap: 00320CE0
Memory contents:
00320AB0: Just Testing, 0
00320CE0: Heap1, 20
Better Idea constructed
Heap2 constructed
Memory contents:
00320AD0: Better Idea, 6
00320EC8: Heap2, 10
Heap1 destroyed
Heap2 destroyed
Better Idea destroyed
Just Testing destroyed
Done
对于使用定位new运算符创建的对象,由于晚创建的对象可能依赖于早创建的对象,所以在删除时应以与创建顺序相反的顺序进行删除。
相关文章
- 理论:第九章:JVM内存模型,算法,垃圾回收器,调优,四大引用,常见的JVM错误,类加载机制(双亲委派),创建一个对象,这个对象在内存中是怎么分配的?
- 基于强化学习的信息流广告分配方法CrossDQN[美团]
- C++ 为进程、线程分配 CPU 资源
- 【C 语言】内存管理 ( 动态内存分配 | 栈 | 堆 | 静态存储区 | 内存布局 | 野指针 )
- 【Linux 内核 内存管理】物理分配页 ③ ( 物理页分配标志位分析 | ALLOC_WMARK_MIN | ALLOC_WMARK_MASK | ALLOC_HARDER )
- SQLServer 错误 5256 表错误:分配单元 ID A_ID,页 P_ID1 在页头中包含错误的页 ID。 该页头中的 PageId 为 P_ID2。 故障 处理 修复 支持远程
- [C语言] 数据结构-预备知识动态内存分配详解编程语言
- Oracle 分配函数权限的方法(oracle函数权限)
- 函数创建用户并分配权限详解编程语言
- CRM 员工创建并分配用户详解编程语言
- Mysql创建库及授权权限分配(mysql创建库并授权)
- MySQL内存分配与优化(mysql内存分配)
- Linux重新格式化分区一步步实现硬盘再分配(linux重新格式化分区)
- mysql新增、删除用户和权限分配
- C#字符串内存分配与驻留池学习分享
- mysql数据库添加用户及分配权限具体实现
- ExtJS4利根据登录后不同的角色分配不同的树形菜单