按位复制和浅复制之间有什么区别吗?

Is any difference between bitwise copy and shallow copy?

关于问题 What is the difference between memberwise copy, bitwise copy, shallow copy and deep copy? 第一个答案的作者说:

Shallow Copy

Refers to copying just the immediate members of an object, without duplicating whatever structures are pointed by them. It is what you get when you do a bit-wise copy.

为什么我们有 2 个术语 用于与 按位复制浅复制 相同的事情].真的没有区别吗?

是否同样适用于其他语言(不仅仅是 C++),我可以将这些词普遍互换使用吗?

如果我没理解错的话,按位拷贝就是浅拷贝。按位复制只是简单地复制每一位以创建一个副本,而浅表副本在复制直接成员时更为通用,如您的引述中所述。

按位复制是浅复制,但反之不一定。

原因是由于填充,对象中可能有您通常忽略的位,但它们是对象的一部分。

比如这个

struct bar {
    int x;
    foo b;
};

记忆中可以这样:

| x | some padding | b | more padding |

当您通过例如 memcpy 复制位时,填充位也将被复制。通过比较 xb 成员,您无法分辨出差异,但在位级别上存在差异。当您按位(而不是按成员)比较两个对象时,您会注意到它。


正如 Yakk - Adam Nevraumont 在评论中指出的那样,填充并不是浅拷贝与按位拷贝不同的唯一原因。例如

struct foo{ 
    foo* self;
    foo() : self(this) {}
    foo& operator=(const foo& other) {}
    foo(const foo& other) : self(this) {}
};

成员 self 应该指向对象本身,这是 class 的不变量(为简单起见省略了适当的封装)。仅仅复制指针会使 self->this 指向 other 而不是 this,因此打破了不变量。 operator= 不需要复制任何东西,复制构造函数只需要正确初始化 self。这可以被认为是浅拷贝,因为我们只是在“复制”指针 self 而不是它指向的内容(实际上深拷贝在这里是致命的)。但是,按位复制会有所不同,它会在将 b 复制到 a 之后导致 a.self 指向 b(这将再次破坏不变量)。


考虑这个例子:

#include <iostream>
#include <cstring>

struct X {
    int a = 1;
    double b = 2;
    float c = 3;
    X& operator=(const X& x){
        a = x.a;
        b = x.b;
        c = x.c;
        return *this;
    }
};

int main()
{
    X a;
    X b;
    std::cout << "sizeof(X) " << sizeof(X) << "\n";
    std::cout << "sizeof(int) " << sizeof(int) << "\n";
    std::cout << "sizeof(double) " << sizeof(double) << "\n";
    std::cout << "sizeof(float) " << sizeof(float) << "\n";
    
    //memcpy(&a,&b,sizeof(X));
    a = b;
    char* aptr = reinterpret_cast<char*>(&a);
    char* bptr = reinterpret_cast<char*>(&b);
    for (size_t i = 0; i < sizeof(X); ++i) {
        if (aptr[i] != bptr[i]) std::cout << " !!! ";
    }
}

可能的输出是:

sizeof(X) 24
sizeof(int) 4
sizeof(double) 8
sizeof(float) 4
 !!! 

X的大小不是其成员大小的总和。那就是填充。有关详细信息,请参阅 Struct padding in C++.

operator= 进行成员复制。因为该对象包含不用于成员的字节,所以在复制成员并查看位表示后,您可以观察到 ab 之间的差异。

另一方面,如果通过 memcpy 进行复制,那么 ab 的位表示将保证相同(即没有输出 !!!).