【问题标题】:What does std::make_unique_for_overwrite() do vis-a-vis std::make_unique()?std::make_unique_for_overwrite() 相对于 std::make_unique() 做了什么?
【发布时间】:2020-01-22 20:10:55
【问题描述】:

似乎在 C++20 中,我们获得了一些额外的智能指针实用功能,包括:

template<class T> unique_ptr<T> make_unique_for_overwrite();
template<class T> unique_ptr<T> make_unique_for_overwrite(size_t n);

std::make_sharedstd::shared_ptr 也是如此。为什么不是现有功能:

template<class T, class... Args> unique_ptr<T> make_unique(Args&&... args); // with empty Args
template<class T> unique_ptr<T> make_unique(size_t n);

够了吗?现有的不使用对象的默认构造函数吗?

注意:在这些函数的早​​期提案中,名称为make_unique_default_init()

【问题讨论】:

    标签: c++ smart-pointers c++20


    【解决方案1】:

    这些新功能不同的:

    • 原始make_XYZ始终初始化指向的值(“显式初始化”,参见standard 中的§ class.expl.init)。
    • 新的make_XYZ_for_overwrite:执行指向值的“默认初始化”(见standard中的§dcl.init,第7段);在典型的机器上,这意味着对于非类、非数组类型实际上没有初始化。 (是的,这个词有点混乱;请阅读链接中的段落。)

    这是普通指针的一个特性,智能指针实用程序函数不具备该特性:使用常规指针,您可以在不实际初始化指向值的情况下进行分配:

    new int
    

    对于唯一/共享指针,您只能通过包装现有指针来实现这一点,如下所示:

    std::unique_ptr<int[]>(new int[n])
    

    现在我们有一个包装函数。

    注意:请参阅相关的 ISO C++ WG21 proposal 以及 this SO answer

    【讨论】:

    • @LightnessRacesinOrbit:我编辑说“在典型机器上”。
    • 我不知道你为什么不使用正确的术语来说明它是什么:(
    • @LightnessRacesinOrbit:因为它们令人困惑。我想解释一下,如果我们使用_default_init,就不会浪费时间来初始化ints 的数组。
    • 当您使用它们来表示与它们实际相反的含义时,它们尤其令人困惑!
    • @LightnessRacesinOrbit:现在我很困惑。
    【解决方案2】:

    allocate_sharedmake_sharedmake_unique 都通过执行与 new T(args...) 等效的操作来初始化底层对象。在零参数的情况下,这减少到new T() - 也就是说,它执行value initialization。许多情况下的值初始化(包括像intchar 这样的标量类型,它们的数组和它们的聚合)执行zero initialization - 也就是说,这是为了将一堆数据归零而进行的实际工作.

    也许你想要那个,这对你的应用程序很重要,也许你不想要。来自P1020R1,介绍最初命名为make_unique_default_initmake_shared_default_initallocate_shared_default_init的函数的论文(在C++20的全国投票评论过程中,这些函数从meow_default_init重命名为meow_for_overwrite):

    诸如unsigned chardouble 等内置类型的数组在分配后立即由用户全部初始化的情况并不少见。在这些情况下,allocate_sharedmake_sharedmake_unique 执行的值初始化是多余的并且会损害性能,因此需要一种选择默认初始化的方法。

    也就是说,如果您正在编写如下代码:

    auto buffer = std::make_unique<char[]>(100);
    read_data_into(buffer.get());
    

    make_unique 执行的值初始化会将这 100 个字节归零,这是完全没有必要的,因为无论如何你都会立即覆盖它。

    新的meow_for_overwrite 函数改为执行default initialization,因为无论如何都会立即覆盖所使用的内存(因此得名)——也就是说相当于执行new T(没有任何括号或大括号)。我之前提到的那些情况下的默认初始化(如intchar,它们的数组和它们的聚合)不执行初始化,这样可以节省时间。


    对于具有用户提供的默认构造函数的类类型,值初始化和默认初始化之间没有区别:两者都只会调用默认构造函数。但对于许多其他类型,可能会有很大差异。

    【讨论】:

    • 我根本不在乎的冗余初始化。默认初始化的问题(对我来说)是实例化核心零套接字上的内存页面。如果您正在执行多线程数值计算(例如使用 OpenMP),这意味着在其他套接字上运行的线程将不断地从套接字零拉出 NUMA 内存。在一个简单的演示程序(热方程)中,我发现性能下降了 2 倍。
    • @VictorEijkhout - 有趣的评论 - 我不明白为什么该问题的解决方案不是在其他套接字上运行的线程自己为那些 unique_ptrs 分配内存?这是他们不能做的某种 OpenMP 的事情吗?还是代码通常不是这样写的?
    • @davidbak 唯一指针由一个线程创建,然后默认初始化也由该线程完成,因此所有页面都在该线程运行的套接字的内存中实例化。 (这是相当晦涩的东西。你知道 NUMA 是什么吗?这基本上是问题的根源。)
    • @VictorEijkhout - 是的,我知道 NUMA 是什么,我只是不明白为什么你不能创建占位符空的唯一 ptrs,将 那些 传递给你的线程,然后这些线程本身分配本地节点内存以放入那些 unique_ptrs。
    • @davidbak 指向什么的唯一指针?
    猜你喜欢
    • 2016-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-27
    • 2020-09-19
    • 2019-11-28
    • 2013-04-22
    相关资源
    最近更新 更多