【问题标题】:boost::make_shared is not calling (placement) operator new?boost::make_shared 没有调用(放置)运算符 new?
【发布时间】:2012-03-12 21:42:19
【问题描述】:

我第一次使用 boost::make_shared 创建共享指针指向的对象。主要是因为我们的代码太慢了,单次分配确实有助于提高性能。

在“硬手动方式”修复了一些内存泄漏之后,我决定通过覆盖所有相关类的新运算符来实现一个简单的内存泄漏检测器,仅用于计算在我们的应用程序的特定点上哪些对象仍然存在。我之前已经实现过几次,惊讶地发现我的代码不再检测到任何对象。

我认为我所要做的就是覆盖“placement new”而不是“普通”运算符 new,因为 make_shared 的 boost 网站文档中的以下内容:

"效果:分配适合 T 类型对象的内存和 通过放置 new 表达式 new( pv ) 在其中构造一个对象 T() 或 new( pv ) T( std::forward(args)... )。分配共享 使用 a 的副本来分配内存。如果抛出异常,则没有 效果。”

但是,我的新展示位置也没有被调用。我编写了一个小测试程序来重现该行为:

#include <iostream>
using namespace std;
#include "boost/shared_ptr.hpp"
#include "boost/make_shared.hpp"

class Test
{
public:
    Test() { cout << "Test::Test()" << endl; }

    void* operator new (std::size_t size) throw (std::bad_alloc) {
        cout << "Test new" << endl;
        return malloc(size);
    }

    void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw() {
        cout << "Test non-throwing new" << endl;
        return malloc(size);
    }

    void* operator new (std::size_t size, void* ptr) throw() {
        cout << "Test non-throwing placement new" << endl;
        return malloc(size);
    }
};

void* operator new (std::size_t size) throw (std::bad_alloc) {
    cout << "Global new" << endl;
    return malloc(size);
}

int main() {
    cout << "..." << endl;
    boost::shared_ptr<Test> t1(boost::make_shared<Test>());
    cout << "..." << endl;
    boost::shared_ptr<Test> t2(new Test());
    cout << "..." << endl;

    return 0;
}

呈现以下输出:

...
Global new
Test::Test()
...
Test new
Test::Test()
Global new
...

我期待在输出的第 3 行出现“Test non-throwing placement new”。你认为行为应该是什么?您是否同意根据 make_shared 的文档,它应该调用我的 Test 类的placement new 运算符?还是我误会了?

当然,我可以在本地复制 boosts 实现并添加对放置 new 运算符的调用。但是,这是否合适,还是会违反放置 new 的预期语义?

提前感谢您的时间和帮助。

【问题讨论】:

  • 通过查看 boost 的 ,它使用了全局布局 new 运算符 ::new( pv ) T()。这就是为什么没有调用你的类级别放置...通过在 new 之前删除全局限定符 '::',make_shared 实际上会调用你的类级别放置 new 运算符。

标签: c++ boost new-operator make-shared


【解决方案1】:

作为make_shared 的来源,它使用全局放置new 运算符,而不是您的类提供的新运算符。

::new( pv ) T();

不幸的是 (至少在 OS X 上) (according to the standard),您无法定义自己的全局放置 new 运算符。 allocate_shared 似乎更符合您的要求。

编辑

另一种方法是实际编写make_shared 的一个版本,它使用类的新位置而不是全局位置。它只有大约 10 行代码,只要您尊重 the license of the original code,应该没问题。

【讨论】:

  • 值得注意的是,allocate_shared() 可能意味着在更大的代码库中替换相当多的调用。
  • 感谢您的有用回答。我不知道我不应该覆盖新的展示位置。所以这绝对是我尝试解决方案的一个亮点。我确实已经研究了 allocate_shared 选项,但是 - 正如 Georg 所说 - 这对现有代码的影响太大。
【解决方案2】:

您不能替换placement new(§18.4.​1.3,参见例如this question),所以给出的输出看起来不错。

作为修改 Boost 标头的替代方法,您可以查看 Valgrind 等外部​​工具。

【讨论】:

    【解决方案3】:

    为您的特定类型实现的operator new 将仅用于与new 一起动态分配类型元素的表达式,例如Test *p = new Test;。现在make_shared 不会动态分配您类型的对象,而是缓冲区共享计数(包括计数器、删除器和一些额外的点点滴滴)和你的对象。

    然后它使用 placement-new 来调用对象的构造函数。请注意,在这种情况下 placement new 不是分配内存,它只是 C++ 中有趣的语法,用于在已分配的内存块上调用构造函数。这实际上可能是造成混淆的原因,因为 new 表达式、您的 operator newplacement-new 是碰巧共享名称的三个不同概念。

    【讨论】:

    • 尽管如此,即使对于placement-new,仍然可以提供每个类的重载。也许不寻常,但话又说回来,C++ 的哪个部分没有不寻常:-) 要带回家的要点是std::allocator 不使用任何每个类的分配函数,但只有全球::new.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-20
    • 1970-01-01
    • 2014-08-16
    • 1970-01-01
    • 1970-01-01
    • 2018-07-16
    相关资源
    最近更新 更多