C++法则16:当分配一个对象时,尽量NewEmplace代替 new。

发布于:2025-06-26 ⋅ 阅读:(20) ⋅ 点赞:(0)

C++法则16:当分配一个对象时,尽量以NewEmplace代替new,好处是免去初始化。

 NewEmplace

/// <summary>
/// 直接构建一个新的MyClass,并返回它的一个指针。(好处免去初始化)
/// 用法例子:
///
/// list<int>* pl = NewEmplace<list<int>>(5,10); //申请一个列表内存指针(5个元素都是10的列表)
/// delete[] pl; //删除内存对象,分配时用new[]分配的
/// </summary>
/// <typeparam name="MyClass"></typeparam>
/// <typeparam name="...MyClassConstructorParameterTypes"></typeparam>
/// <param name="...MyClassConstructorParameters"></param>
/// <returns></returns>
/// 创建时间: 2024-10-01      最后一次修改时间:2024-10-01
template<typename MyClass, typename... MyClassConstructorParameterTypes>
static  MyClass* NewEmplace(MyClassConstructorParameterTypes&&... MyClassConstructorParameters)
{
	_obj_used += 1;
	_mem_used = _mem_used + sizeof(MyClass);


	/*
	//不能用 delete[] 删除
	return new MyClass(
		std::forward<MyClassConstructorParameterTypes>(MyClassConstructorParameters)...);

	*/

	/*
	list<int> il = { 1,2,3 };
	std::list<int>* p2 = new std::list<int>[1] {il};
	*/

	//delete[] pl; //删除内存对象,分配时用new[]分配的
	return new MyClass[1]
	{
		MyClass(
		std::forward<MyClassConstructorParameterTypes>(MyClassConstructorParameters)...) 
	};

}

Delete版本跟new一样:


	/// <summary>
	/// 功能:释放内存,并把内存指针重置为0
	/// 
	/// 条款3:尽量以 new 和 delete 取代malloc 和 free
	///		分配内存,给对象分配内存不能用C语言的malloc函数, 因为 malloc函数不会调用用对象的
	///		构造函数,free函数不会调用对象的析构函数。	  
	/// 条款5:使用相同形式的 new 和 delete
	///		游戏规则:如果你在调用 new 时使用了 [ ] ,则你必须在调用 delete 时也使用[] 。
	///		如果你在调用 new 的时候没有使用[],那么你也不应该在调用 delete 时使用[] 。
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="pMemory"></param>
	/// <param name="nCount"></param>
	/// 创建时间:????-??-??    最后一次修改时间:2025-06-24
	template<typename T> static  void Delete(T*  pMemory, const size_t& nCount = 1)
	{ 
		_obj_used -= nCount;
		_mem_used -= sizeof(T) * nCount;
		delete[] pMemory;		
	}

例子:链表添加节点,看代码。 

void my_list_test() {
	test t1;
	_list<test> s;
	s.push_back(t1); //等价于 s.add(t1);
	  
}


void std_list_test() {
	test t1;
	std::list<test> s;
	s.push_back(t1);
}


int main(int argc, char* argv[]){

	QApplication app(argc, argv); 

	my_list_test();

	std::cout << "-------------------------\n";

	std_list_test(); 

    return app.exec();    
}

输出:

test

test

test

-------------------------

test

我的_list初始化 test 三次,而std::list只有一次。

问题在于:

 
    /// <summary>
    /// 将指定集合的元素添加到末尾
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="item"></param>
    /// <returns></returns>
    /// 创建时间: ????-??-??      最后一次修改时间:2024-09-22
    inline virtual _DListNode<T>* add(const T& tValue)
    {
        _DListNode<T>* pResult;

        if (_count == 0)
        {
            //_first = new _DListNode<T>(item);
            pResult = _Memory::New< _DListNode<T> >(1);

            _first = pResult;
            _first->data = tValue;
            _first->next = null;
            _first->prev = null;
            _last = _first;
        }
        else
        {
            pResult = _Memory::New< _DListNode<T> >(1);

            pResult->data = tValue;
            pResult->next = null;
            pResult->prev = _last;

            _last->next = pResult;
            _last = pResult;
        }
        ++_count;

        this->_so = _SortOrder::s_null;   //要重新排序

        return pResult;
    }

用的是new:

/// <summary>
/// 功能:分配内存。
/// 
/// 条款3:尽量以 new 和 delete 取代malloc 和 free
///		分配内存,给对象分配内存不能用C语言的malloc函数, 因为 malloc函数不会调用用对象的
///		构造函数,free函数不会调用对象的析构函数。	  
/// 条款5:使用相同形式的 new 和 delete
///		游戏规则:如果你在调用 new 时使用了 [ ] ,则你必须在调用 delete 时也使用[] 。
///		如果你在调用 new 的时候没有使用[],那么你也不应该在调用 delete 时使用[] 。
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="nCount">分配个数</param>
/// <returns></returns>
/// 创建时间: ????-??-??      最后一次修改时间:2024-12-08
template<typename T> static  T  * New(const size_t&  nCount, const bool& bZero = false)
{
	if (nCount <= 0)  return null;
	 
	if (nCount > _memory_allow_max){ 

		throw "超出最大充许申请的对象数"; 
	}

	 
	_obj_used += nCount;
	_mem_used = _mem_used + sizeof(T) * nCount;

	if (bZero) {
		return  new T[nCount]();	
	}else {
		return new T[nCount];
	}		
}

还有一点,在构造函数中对象要直接初化,例如:

/// <summary>
/// 构造函数
///     //这种写法 data 先初始化为默认值,即:
///     //data = T();
///     //data = aData;
///     _DListNode(const T& aData)
///     {
///         aData = aData;
///         prev = null;
///         next = null;
///     }
/// </summary>
/// <param name="aData">默认数据</param>
_DListNode(const T& aData) : data(aData), prev(null), next(null) {}

所以,我的问题在于:

(1)new 分配内存时初始化一次test

(2)在_DListNode(const T& aData) 再初始化一次test

一共多了两次。

最优做法:

(1)

(2)

现在运行程序,结果是一样的:

你有没有好奇,为什么test只构造了一次,因为C+为test类添加了一个默认的

移动构造函数。

在C++中,编译器是否会为类自动生成默认的移动构造函数取决于类的具体定义。

1. 自动生成默认移动构造函数的条件

同时满足以下所有条件时,编译器会隐式生成一个默认的移动构造函数(ClassName(ClassName&&) noexcept):

  • 类中没有用户显式声明的移动构造函数;

  • 类中没有用户声明的拷贝构造函数;

  • 类中没有用户声明的拷贝赋值运算符(operator=);

  • 类中没有用户声明的析构函数;

  • 类的所有非静态成员(以及基类)必须支持移动操作(即成员/基类具有可用的移动构造函数)。


2. 自动生成的移动构造函数的行为

生成的默认移动构造函数会:

  • 对类的每个成员(和基类)按成员移动(使用std::move);

  • 内置类型(如int、指针等)会直接拷贝(因为它们没有移动语义)。


3. 不会生成默认移动构造函数的情况

如果类满足以下任一条件,编译器不会自动生成默认移动构造函数:

  • 用户显式声明了移动构造函数;

  • 用户声明了拷贝构造函数、拷贝赋值运算符或析构函数(这遵循三五法则);

  • 类中有成员或基类不可移动(例如,包含const成员或引用成员)。

此时,移动操作会回退到拷贝操作(如果拷贝构造函数存在)。

例如:

#include <iostream>

struct Example1 {
    int x;
    std::string s;  // 支持移动的成员
    // 编译器自动生成默认移动构造函数
};

struct Example2 {
    int x;
    std::string s;
    Example2(const Example2&) {}  // 用户声明了拷贝构造函数
    // 不会生成默认移动构造函数
};

struct Example3 {
    const int x;    // const成员不可移动
    // 不会生成默认移动构造函数
};

int main() {
    Example1 a;
    Example1 b(std::move(a));  // 调用隐式生成的移动构造函数

    Example2 c;
    // Example2 d(std::move(c)); // 错误:移动构造函数被删除
    return 0;
}

另:::operator new 分配的内存能用 delete删除吗?还是一定要用::operator delete

在 C++ 中,::operator new 分配的内存 必须 用 ::operator delete 释放,而不能直接用 delete 关键字。同样,new 分配的内存要用 delete 释放,而不能用 ::operator delete


1. 内存分配/释放的匹配规则

分配方式 正确释放方式 错误释放方式 原因
new T delete ptr ::operator delete(ptr) delete 会先调用析构函数,再调用底层 operator delete
new T[n] delete[] ptr delete ptr delete[] 会正确调用每个元素的析构函数
::operator new(size) ::operator delete(ptr) delete ptr ::operator delete 仅释放内存,不涉及析构
placement new 手动析构 + 底层释放 delete ptr placement new 不分配内存,仅构造对象

2. 为什么不能混用?

  • delete 关键字 会做两件事:

    1. 调用对象的析构函数(如果适用)。

    2. 调用底层 operator delete 释放内存。

  • ::operator delete 仅释放内存,不会调用析构函数

如果对 ::operator new 分配的内存使用 delete

  • 未定义行为 (UB)delete 会尝试调用析构函数,但内存可能不是合法对象(如未构造或已析构)。

如果对 new 分配的内存使用 ::operator delete

  • 内存泄漏(如果对象需要析构)。

  • UB(如果对象有非平凡析构函数)。


3. 正确示例

(1) ::operator new + placement new(需手动管理)

// 分配原始内存
void* pMem = ::operator new(sizeof(MyClass));

// 在内存上构造对象
MyClass* pObj = new (pMem) MyClass(42);

// 手动析构并释放
pObj->~MyClass();
::operator delete(pMem);  // 必须用 ::operator delete
(2) 普通 new(自动管理)

MyClass* pObj = new MyClass(42);
delete pObj;  // 正确:调用析构函数 + 释放内存
(3) 错误示例

void* pMem = ::operator new(sizeof(MyClass));
MyClass* pObj = new (pMem) MyClass(42);

delete pObj;  // 错误!UB(会调用析构函数 + 错误的释放逻辑)

4. 关键总结

  • ::operator new 和 ::operator delete 是底层内存操作,不涉及构造/析构。

  • new 和 delete 是高级操作,包含构造/析构 + 内存管理。

  • placement new 必须手动析构,并匹配原始的内存释放方式(如 ::operator delete 或自定义分配器)。


网站公告

今日签到

点亮在社区的每一天
去签到