1

我很难理解为什么不允许使用 memcpy'ing non_copyable 类型,或者即使我的以下代码是不允许的:

struct trivially_copyable_type
{
    int member;
};

struct non_trivially_copyable_type
{
    int member;
    non_trivially_copyable_type() { }
    non_trivially_copyable_type(const non_trivially_copyable_type& other) { }
};
int main()
{
    bool result = std::is_trivially_copyable_v<trivially_copyable_type>; // True
    result = std::is_trivially_copyable_v<non_trivially_copyable_type>; // False

    trivially_copyable_type copyable;

    void* memory = malloc(128);

    memcpy(memory, &copyable, sizeof(copyable));
    trivially_copyable_type* p1 = (trivially_copyable_type*)memory;
    p1->member = 7; // This is allowed


    non_trivially_copyable_type noncopyable;

    memcpy(memory, &noncopyable, sizeof(noncopyable));

    non_trivially_copyable_type* p2 = (non_trivially_copyable_type*) memory;

    p2->member = 7; // This is undefined or illegal?
} 

如果我们 malloc 内存并通过指向 int 的指针访问该内存,如下所示:

int * ptr = (int*) malloc(128);
*ptr = 7; // We didn't create an int, but can treat that memory as int

以同样的方式:

trivially_copyable_type* ptr = (trivially_copyable_t*) malloc(128);
*ptr = 7; // Same, we didn't create a 'trivially_copyable_type object at memory, but can read and write to it

但:

non_trivially_copyable_type* ptr = (non_trivially_copyable_t*) malloc(128);
*ptr = 7; // Is whether this is legal dependent on whether the type is trivially_constructible or not?

我不明白为什么或者如果在前面的例子中我 memcpy 进入缓冲区是非法的。

4

3 回答 3

2

据我了解,普通可复制类型是可以安全处理的类型,例如标准库,作为“C 风格”类型,在复制过程中没有副作用。因此没有(用户定义的)构造函数、析构函数、赋值运算符。它们是我们过去所知的 POD 类型,但已根据现代 C++ 的要求和需求进行了更新。

假设您想计算operator=一些标准算法调用的数量。说,std::copy。你超载operator=了一个类,它做了一些计数。图书馆可以std::copy通过求助于优化实现memcpy吗?嗯,无条件?那么你所有的计数都会丢失!一般来说,这将是一场灾难,这不是我们在 C++ 中所期望的!C++ 中的类通常有一些不变量,并且构造函数可以强制它们。复制和内存移动不是一回事。

当这样的优化是 C++ 安全的?仅当对象在循环中逐个元素移动(复制)或 memcpy-ed 无关紧要。

std::copy以下是cppreference 中的部分描述:

将由 [first, last) 定义的范围中的元素复制到从 d_first 开始的另一个范围。
...
在实践中,如果值类型是 TriviallyCopyable 并且迭代器类型满足 LegacyContiguousIterator,则 std::copy 的实现会避免多次赋值并使用批量复制函数,例如 std::memmove。

while所以让我重复一遍:对于普通可复制类型,如果您在循环中使用逐元素复制或memcpy.

它是否保证复制平凡可复制的对象总是安全的?当然不是!以一个简单的链表结构为例:

struct Node
{
   int value;
   Node* next;
};

这是一种可简单复制的类型。您可以将这些结构放在一个向量中,以便它们形成一个完美的链表。但是,如果您将此向量复制到不同的位置或由于某些原因而自动重新分配数据push_back,那么您注定要失败。while但是,如果您在一个简单的循环中复制这些对象,这是您可以并且应该期待的!

于 2021-04-06T01:00:37.923 回答
2

问题很困惑。平凡可复制保证当您来回复制对象的位时,您会得到相同的对象。但是你必须已经有一个对象。在所有情况下,您只是将位填充到您希望对象所在的位置,这是对象生命周期的问题。你什么时候可以假设这样的物体存在?

如果我们 malloc 内存并通过指向 int 的指针访问该内存,如下所示:

int * ptr = (int*) malloc(128);
*ptr = 7; // We didn't create an int, but can treat that memory as int

在P0593之前这是错误的,它被用作 C++17 的缺陷解决方案。的生命周期int必须开始。在 P0593 之前,这必须通过放置 new来完成。现在,它隐式启动,因为它int是一个隐式生命周期类型malloc开始一个隐式生命周期

以同样的方式:

trivially_copyable_type* ptr = (trivially_copyable_t*) malloc(128);
*ptr = 7; // Same, we didn't create a 'trivially_copyable_type object at memory, but can read and write to it

这实际上与可复制无关,因为没有复制。这没关系,因为trivially_copyable_type它是一个隐式生命周期类型的聚合,包含一个intwhich 也是。

但:

non_trivially_copyable_type* ptr = (non_trivially_copyable_t*) malloc(128);
*ptr = 7; // Is whether this is legal dependent on whether the type is trivially_constructible or not?

这与微不足道的可复制性无关。没有对象,因为non_trivially_copyable_type它不是隐式生命周期类型。

于 2021-04-06T15:19:01.777 回答
1

考虑:

struct non_trivially_copyable_type
{
    int member;
    non_trivially_copyable_type *self;
    non_trivially_copyable_type() { self = this; }
    non_trivially_copyable_type(const non_trivially_copyable_type& other) { }
    int get_member() { return self->member; }
};

现在你看到问题了吗?如果你memcpy这个类型然后销毁你复制的对象,下一次调用get_member会做错事。

该类有一个构造函数。这意味着它的实例在构造之前不存在。您不应访问不存在的对象的成员。

于 2021-04-05T23:58:53.987 回答