2017-08-06 27 views
9

C++参考页面列出了针对global new operators的8 类特定过载。其中四个是为2017版C++添加的。如果我为一个类编写运算符new和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, 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个类特定版本global delete operators,其中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++类,做我需要重载那些所有?我忽略了可替换的全球运营商,因为我只写类专用运营商。

This other question provides info on writing ISO compliant new and delete operators,但并没有说我是否应该重载所有这些,或只是一些。

this question about class specific new and delete operators的回答并未说明是否全部或部分替换。

如果您可以提供来自C++标准的引文或C++内存专家的评论,这将有所帮助。

+1

对我来说似乎[这个答案](https://stackoverflow.com/a/7151831/366904)有你正在寻找的信息。 –

+2

好吧,如果你想使用自定义分配方案,我认为对所有进行实际分配和释放操作的操作符进行重载是谨慎的。那,或者明确地删除那些你不想支持的。 – StoryTeller

+0

你可以使用'template'来定义'new'和'delete'的位置。这将允许编译器生成类型专用代码。 –

回答

2

不,你不需要写新的所有变化和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) 

,故意省略或=delete这些版本。

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参数也有助于在那里删除。 (如果它预先分配了一个大块,然后将该块分割成单独的块,就像一个数组,我称之为“块模型”,如果每个块指向前一个块和下一个块,我称之为“链模型”链表或链)。因此,当有人从内存块链中删除块时,我想让删除操作员知道被删除的块是正确的大小。我可以在断言(下一个块的大小==地址 - 这个块的地址)的删除操作中放置一个断言。

在适当情况下,优先选择具有对齐参数的新操作符和删除操作符。

既然C++ 17为新的操作符提供了一个对齐参数,如果您需要它们,请使用它们。如果您需要性能,请将对象对齐在4,8或16个字节边界上,这样做!它使程序更快一点。

假设你有一个对齐感知的内存分配器。它知道一些对象最好存储在4字节的边界上,因为这些对象很小,如果使用4字节的边界,则可以将更多内容挤入内存。它也知道一些对象最好在8字节的边界上对齐,因为这些对象经常被使用。

如果内存处理程序提供了正确的新运算符,并且派生类为对齐提供了正确的值,则它将知道这一点。

的2017 C++标准表示:

当分配对象,它们的对准超过STDCPP_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; 
} 
+0

谢谢。迄今为止的最佳答案。非常详细和可以理解。 – LincolnMan

2

您只需要将您使用的版本newdelete过载。根据[class.free]中的示例,在一个类中定义一个operator new函数将隐藏所有全局函数operator new函数。这与定义与基类函数具有相同名称的方法或全局函数隐藏基本或全局版本的方法相同。

请注意,operator newoperator new[]是不同的名称,所以重载operator new本身不会隐藏全局operator new[]函数。

2

如果我用new和delete运算符编写C++类,是否需要重载所有这些?

不,你不需要重载所有它们。至少,您需要重载需要定制的操作员。

我想我们可以假设你在重载的操作符中做了一些特定的操作,否则你不需要它们。

问题变得更多我应该重载所有这些吗?

是的,你可能应该。如果代码根据代码中使用的newdelete的形式做了完全不同的事情,这将是令人惊讶的,例如,

auto* obj1 = new Obj{}; 
// vs 
auto* obj2 = new Obj[5]; 

如果new运营商应用某些特殊的初始化,就有理由预计,这两种形式会做初始化。

另一方面,如果其他形式不适用,那么有利于完全删除(= delete)这些重载。

The C++ operators进入“集合”,算术,流插入和提取,关系等等。通常的做法是,当集合中的一个运算符被重载时,其他运算符也是如此。

它并不总是适用,但通常是这样。例如。拼接操作通常有operator+operator+=,但不是operator-operator-=

+0

赞同删除它们,除了使它们私人。 – StoryTeller

+0

好点,我会更新。 – Niall

相关问题