如果我为一个类编写new和delete运算符,我是否必须编写它们的所有重载
If I write operators new and delete for a class, do I have to write all of their overloads?
C++参考页面列出了全局新运算符的8个类特定的重载。其中四个是为2017版本的C++添加的。
特定类别分配函数
void* T::operator new ( std::size_t count );
void* T::operator new[]( std::size_t count );
void* T::operator new ( std::size_t count, std::align_val_t al ); // (since C++17)
void* T::operator new[]( std::size_t count, std::align_val_t al ); // (since C++17)
特定类别的职位分配功能
void* T::operator new ( std::size_t count, user-defined-args... );
void* T::operator new[]( std::size_t count, user-defined-args... );
void* T::operator new ( std::size_t count,
std::align_val_t al, user-defined-args... ); // (since C++17)
void* T::operator new[]( std::size_t count,
std::align_val_t al, user-defined-args... ); // (since C++17)
该网站还列出了10个特定于类的版本的全局删除运算符,其中4个是在2017年推出的。
特定于类的通常解除分配函数
void T::operator delete ( void* ptr );
void T::operator delete[]( void* ptr );
void T::operator delete ( void* ptr, std::align_val_t al ); // (since C++17)
void T::operator delete[]( void* ptr, std::align_val_t al ); // (since C++17)
void T::operator delete ( void* ptr, std::size_t sz );
void T::operator delete[]( void* ptr, std::size_t sz );
void T::operator delete ( void* ptr, std::size_t sz, std::align_val_t al ); // (since C++17)
void T::operator delete[]( void* ptr, std::size_t sz, std::align_val_t al ); // (since C++17)
特定类别的配售解除分配函数
void T::operator delete ( void* ptr, args... );
void T::operator delete[]( void* ptr, args... );
如果我编写一个带有new和delete运算符的C++类,我是否需要重载所有运算符?我忽略了可替换的全局运算符,因为我只编写类特定的运算符。
另一个问题提供了关于编写符合ISO的新运算符和删除运算符的信息,但没有说明我是否应该重载所有运算符,或者只重载一些运算符。
这个关于类特定的new和delete运算符的问题的答案并没有说明是替换全部还是只替换其中的一部分。
如果你能提供C++标准的引文或C++记忆专家的评论,那将有所帮助。
不,您不需要为类编写所有新运算符和删除运算符的变体
与其他版本相比,更喜欢某些新版本和删除版本有多种原因。我将分别描述每一个原因。
与没有大小参数的操作符相比,几乎总是更喜欢有大小参数的delete操作符
当我为一个为其他类提供内存处理的基类编写删除运算符时,我使用这些版本的删除运算符
void T::operator delete ( void* ptr, std::size_t sz );
void T::operator delete[]( void* ptr, std::size_t sz );
void T::operator delete ( void* ptr, std::size_t sz, std::align_val_t al ); // (since C++17)
void T::operator delete[]( void* ptr, std::size_t sz, std::align_val_t al ); // (since C++17)
并故意省略或CCD_ 1这些版本。
void T::operator delete ( void* ptr );
void T::operator delete[]( void* ptr );
void T::operator delete ( void* ptr, std::align_val_t al ); // (since C++17)
void T::operator delete[]( void* ptr, std::align_val_t al ); // (since C++17)
原因是std::size_t sz
参数告诉我对象的大小或数组的大小。在编写基类时,我不知道派生类对象的大小,所以使用size参数会有所帮助。我的一些内存处理程序按大小隔离对象(当所有块大小相同时,更容易集中内存)。我可以使用size参数来快速选择要搜索的内存池,而不是搜索所有内存池。这将O(n)算法转化为O(1)动作。
我的一些内存分配器使用"链模型"而不是"块模型",size参数也有助于删除那里的内容。(如果内存分配器预先分配了一个巨大的块,然后像数组一样将该块分割成单独的块,我将其称为"块模型"。如果每个块都指向前一个和下一个块,比如链表或链,我将内存处理程序称为"链模型"。)因此,当有人从内存块链中删除一个块时,我想让delete操作符知道被删除的区块大小是正确的。我可以在断言的删除操作中放入一个断言(size==下一个区块的地址-这个区块的地址)。
在适当的情况下,使用带有对齐参数的new和delete运算符
既然C++17为新操作符提供了一个对齐参数,那么如果需要的话就使用它们。如果您需要性能,请在4、8或16字节的边界上对齐对象,这样做!它使程序更快一点。
假设您有一个可识别对齐的内存分配器。它知道一些对象最好存储在4字节边界上,因为这些对象很小,如果使用4字节边界,可以将更多的对象压缩到内存中。它还知道一些对象最好在8字节的边界上对齐,因为这些对象经常被使用。
如果内存处理程序提供正确的新运算符,并且派生类提供正确的对齐值,那么它就会知道这一点。
2017 C++标准说:
当分配对齐度超过STDCP_DEFAULT_NEW_alignment的对象和对象数组时,重载解析执行两次:首先是对对齐感知函数签名,然后是对对齐感知功能签名。这意味着,如果具有扩展对齐的类具有不知道对齐的类特定分配函数,则将调用该函数,而不是全局对齐的分配函数。这是有意的:类成员应该最清楚如何处理该类。
这意味着编译器将检查带有对齐参数的新运算符和删除运算符,然后检查没有对齐参数的运算符。
如果您有一个可识别对齐的内存处理程序,那么请始终提供这些新的运算符,即使您还想为客户端代码提供忽略对齐的选项。
void* T::operator new ( std::size_t count, std::align_val_t al ); // (since C++17)
void* T::operator new[]( std::size_t count, std::align_val_t al ); // (since C++17)
void* T::operator new ( std::size_t count,
std::align_val_t al, user-defined-args... ); // (since C++17)
void* T::operator new[]( std::size_t count,
std::align_val_t al, user-defined-args... ); // (since C++17)
如果提供上述新运算符并省略或=delete
这些重载,则可以强制代码提供对齐参数。
void* T::operator new ( std::size_t count );
void* T::operator new[]( std::size_t count );
void* T::operator new ( std::size_t count, user-defined-args... );
void* T::operator new[]( std::size_t count, user-defined-args... );
使用类特定的放置新操作符来提供提示
假设您编写了一个分配多个数据成员的类,并且希望所有这些数据成员都位于同一内存页上。如果数据分布在多个内存页上,CPU将不得不将不同的内存页加载到L1或L2缓存中,以便您可以访问对象的成员数据。如果内存处理程序可以将对象的所有数据成员放在同一个页面上,那么程序将运行得更快,因为CPU不需要将多个页面加载到缓存中。
这些是类特定的放置新操作符。
void* T::operator new ( std::size_t count, user-defined-args... );
void* T::operator new[]( std::size_t count, user-defined-args... );
void* T::operator new ( std::size_t count,
std::align_val_t al, user-defined-args... ); // (since C++17)
void* T::operator new[]( std::size_t count,
std::align_val_t al, user-defined-args... ); // (since C++17)
通过提供提示参数来重载它们,使其看起来像这样。
void* T::operator new ( std::size_t count, void* hint );
void* T::operator new[]( std::size_t count, void* hint );
void* T::operator new ( std::size_t count, std::align_val_t al, void* hint ); // (since C++17)
void* T::operator new[]( std::size_t count, std::align_val_t al, void* hint ); // (since C++17)
提示参数告诉内存处理程序不要将对象放在该提示地址的位置,而是将与提示地址放在同一页上。
现在,您可以编写一个类似这样的类,它是从内存处理类派生而来的。
class Foo : public MemoryHandler
{
public:
Foo();
...
private:
Blah * b_;
Wham * f_;
};
Foo::Foo() : b_( nullptr ), f_( nullptr )
{
// This should put data members on the same memory page as this Foo object.
b_ = new ( this ) Blah;
f_ = new ( this ) Wham;
}
您只需要重载您使用的new
和delete
的版本。根据[class.free]中的示例,在类中定义operator new
函数将隐藏所有全局operator new
函数。这与定义与基类函数或全局函数具有相同名称的方法相同,可以隐藏基类或全局版本。
请注意,operator new
和operator new[]
是不同的名称,因此单独重载=delete
0不会隐藏全局operator new[]
函数。
如果我用new和delete运算符编写C++类,我需要重载所有这些吗?
不,您不需要重载所有它们。至少,您需要重载需要自定义的运算符。
我认为我们可以假设你在过载的运营商中做一些特定的事情,否则你无论如何都不需要它们。
问题变得越来越严重了我应该超载所有这些吗
是的,你可能应该。如果代码根据代码中使用的new
或delete
的形式(例如)做完全不同的事情,那将是令人惊讶的
auto* obj1 = new Obj{};
// vs
auto* obj2 = new Obj[5];
如果new
运算符应用了一些特殊的初始化,那么可以合理地预期两种形式都会执行该初始化。
另一方面,如果其他形式不适用,则赞成完全删除(= delete
)这些重载。
C++运算符有"集合"、算术、流插入和提取、关系运算符等。通常的做法是,当集合中的一个运算符过载时,其他运算符也过载。
它并不总是适用,但通常适用。例如,级联操作通常具有operator+
和operator+=
,但没有operator-
和operator-=
- 重载运算符的范围是什么?它是否会影响作为类成员的集合的插入函数?
- 我的运算符重载是否有效<<(流插入)左操作数不是 ostream
- 重载虚拟行为是否定义良好?
- 检查类是否具有可能重载的函数调用运算符
- C++:如果我重载新运算符,我是否也必须重载删除运算符?
- 预处理器是否可以更改运算符重载功能的符号?
- 如果我也使用复制构造函数并且重载 = 运算符,我是否需要析构函数?
- 在运算符重载中,运算符的左侧是否隐式传递给函数?
- 运算符重载是否真的需要返回值C++?
- 是否使用cv::Point2f类型重载std::排序
- C++编译时检查是否可以用某种类型的参数调用重载函数
- 放置/分段语法是否可能出现懒惰"operator or"重载?
- 如果我为一个类编写new和delete运算符,我是否必须编写它们的所有重载
- 从模板检查成员函数重载是否存在
- CPP 中的 ++ 运算符对字符是否有任何重载?
- 检查类型 T 是否具有成员函数 SFINAE 的任何重载
- 如何在C++编译时检查运算符的特定重载是否存在
- 当取消引用运算符 (*) 重载时,*this 的使用是否受到影响?
- clang 拒绝具有尾随 decltype 返回类型的模板调用是否正确,具体取决于其重载之一?
- 是否重载索引运算符以模拟POD多维数组