C++ 未初始化的拷贝memcpy/memmove优化

C++ 未初始化的拷贝memcpy/memmove优化,c++,visual-c++,stl,C++,Visual C++,Stl,我最近开始研究MSVC实现中的STL。这里有一些不错的技巧,但是我不知道为什么要使用以下标准 如果满足某些条件,则将std::uninitialized_copy优化为简单的memcpy/memmove。据我所知,如果目标类型U是可从源类型T复制的,则输入范围可以是未初始化区域的memcpy 然而,MSVC实现在选择memcpy而不是逐个构建元素之前检查了很多事情。我不想在这里粘贴相关代码,而是通过pastebin共享,如果有人感兴趣: 未初始化的_copy的基本算法如下(为了可读性,省略了异常

我最近开始研究MSVC实现中的STL。这里有一些不错的技巧,但是我不知道为什么要使用以下标准

如果满足某些条件,则将
std::uninitialized_copy
优化为简单的
memcpy/memmove
。据我所知,如果目标类型U
是可从源类型T复制的,则输入范围可以是未初始化区域的
memcpy

然而,MSVC实现在选择
memcpy
而不是逐个构建元素之前检查了很多事情。我不想在这里粘贴相关代码,而是通过pastebin共享,如果有人感兴趣:

未初始化的_copy
的基本算法如下(为了可读性,省略了异常处理)

但以下是可以的:

struct Foo
{
    int i;
    Foo() = default; // or simply omit
};
检查类型U是否可以从类型t简单地复制构造,这难道还不够吗?因为所有这些都是未初始化的拷贝所做的

例如,我不明白为什么MS的STL实现不支持以下内容(注意:我知道原因,它是用户定义的构造函数,但我不理解其背后的逻辑):


在本例中,没有用户定义的复制构造函数,它仍然不是简单的可复制构造函数。感谢您的更正,我根据反馈修改了原始帖子。

未初始化的\u copy
有两个职责:首先,它必须确保正确的位模式进入目标缓冲区。第二,它必须启动该缓冲区中C++对象的生存期。也就是说,它必须调用某种构造函数,除非C++标准专门允许它跳过构造函数调用。 根据我非常不完整的研究,似乎目前只有类型可以保证其位模式由
memcpy
/
memmove
保存;memcping任何其他类型(即使它恰好是可复制构造的和/或可复制分配的!)会正式产生未定义的行为

而且,现在似乎只有类型可以在没有构造函数调用的情况下“突然存在”。(在这方面提出了很多修改,可能是在C++2b中)

< Jonathan Wakely >的评论似乎表明GNU LIGBSTDC++是试图通过不平凡的类型的对象“从不存在”来实现的,即使作为一个C++实现,它们也有能力以性能的名义开发特定于平台的行为。我猜MSVC也遵循类似的逻辑,出于类似的原因(不管这些原因是什么)


通过比较供应商对“可复制但不平凡”的类类型优化
std::copy
std::uninitialized_copy
的意愿,您可以看出供应商不愿意“将对象放入存在”简单的可复制意味着
std::copy
可以使用
memcpy
对现有对象进行分配;但是
std::uninitialized_copy
,为了让这些对象在一开始就存在,仍然需要在循环中调用一些构造函数——即使它是一个微不足道的复制构造函数

class C { int i; public: C() = default; };
class D { int i; public: D() {} };
static_assert(std::is_trivially_copyable_v<C> && !std::is_aggregate_v<C>);
static_assert(std::is_trivially_copyable_v<D> && !std::is_aggregate_v<D>);

void copyCs(C *p, C *q, int n) {
    std::copy(p, p+n, q);  // GNU and MSVC both optimize
    std::uninitialized_copy(p, p+n, q);  // GNU and MSVC both optimize
}
void copyDs(D *p, D *q, int n) {
    std::copy(p, p+n, q);  // GNU and MSVC both optimize
    std::uninitialized_copy(p, p+n, q);  // neither GNU nor MSVC optimizes :(
}

“没有用户定义的构造函数”!=“微不足道的构造”<代码>结构{std::string str;}
没有用户定义的构造函数,但它不是可构造的
memcpy
肯定不会正确复制它。@IgorTandetnik是的。让我们忘记我说过的话,继续使用“可构造的”表单测试类型T是否可以复制构造就足够了,不是吗?与您的问题没有直接关系,但是您的
raw
缓冲区需要使用
alignof(Foo)
才能正确。
struct Foo
{
    int i;
    Foo() = default; // or simply omit
};
struct Foo
{
    int i;

    Foo() noexcept
        : i(10)
    {
    }

    Foo(const Foo&) = default;
};

void test()
{
    // please forgive me...
    uint8 raw[256];
    Foo* dest = (Foo*)raw;
    Foo src[] = { Foo(), Foo() };

    bool b = std::is_trivially_copy_constructible<Foo>::value;  // true
    bool b2 = std::is_trivially_copyable<Foo>::value;           // true

    memcpy(dest, src, sizeof(src)); // seems ok

    // uninitialized_copy does not use memcpy/memmove, it calls the copy-ctor one-by-one
    std::uninitialized_copy(src, src + sizeof(src) / sizeof(src[0]), dest);
}
struct Foo
{
    std::string data;
};
class C { int i; public: C() = default; };
class D { int i; public: D() {} };
static_assert(std::is_trivially_copyable_v<C> && !std::is_aggregate_v<C>);
static_assert(std::is_trivially_copyable_v<D> && !std::is_aggregate_v<D>);

void copyCs(C *p, C *q, int n) {
    std::copy(p, p+n, q);  // GNU and MSVC both optimize
    std::uninitialized_copy(p, p+n, q);  // GNU and MSVC both optimize
}
void copyDs(D *p, D *q, int n) {
    std::copy(p, p+n, q);  // GNU and MSVC both optimize
    std::uninitialized_copy(p, p+n, q);  // neither GNU nor MSVC optimizes :(
}
assert(is_trivially_constructible_v<u64, u64b>);
// Yay!

using u16 = short;
assert(is_trivially_constructible_v<u64, u16>);
// What the...

assert(is_trivially_constructible_v<u64, double>);
// ...oh geez.
using A = int*;
using B = const int*;

void copyAs(A *p, B *q, int n) {
    std::uninitialized_copy(p, p+n, q);  // only MSVC optimizes
}
void copyBs(B *p, B *q, int n) {
    std::uninitialized_copy(p, p+n, q);  // GNU and MSVC both optimize
}