C++ 成功复制动态分配的 obj 而无需复制 ctor?

C++ successfully copying dynamically allocated obj without copy ctor?

我有一个 class 带有指向 char 的指针。我想知道如果不显式定义复制构造函数怎么可能进行复制构造?

我假设是因为对象已经分配了,但如果是这样,那么为什么有人首先需要复制构造函数?

#include <iostream>
#include <cstring>

class Data {
public:
    explicit Data(const char* newData)
        :size{strlen(newData)},
         data{size ? new char[size] : nullptr}
    {
        std::memcpy(data, newData, size);
    }

    friend std::ostream& operator<<(std::ostream& ost, const Data& rhs) {
        for (std::size_t i = 0; i < rhs.size; i++) {
            std::cout << rhs.data[i];
        }
        ost << "\n";
        return ost;
    }

~Data() {
    delete[] data;
}

private:
    std::size_t size;
    char* data;
};

int main() {
    Data data1{"data1"};
    Data data2{data1}; // copy constructor
    std::cout << data1;
    std::cout << data2;

    return 0;
}

输出:

data1
data1

复制构造函数不应该是这样的吗?我经常看到这样的例子。但是既然默认构造函数已经这样做了,那我什么时候才真正需要定义复制构造函数呢?

Data(const Data& other) {
    if (this != &other) {
        size = other.size;
        data = new char[size];
        std::memcpy(data, other.data, size);
    }
}

顺便说一句,我意识到代码引入了一些 maul 实践(例如,使用 new 而不是 smart ptrs,首先不使用 string 等 - 但它只是一个练习)。

自 C++11 起,如果 class 具有用户定义的析构函数,则 implicitly-defined copy constructor 的生成已被弃用(有充分的理由),但它仍会生成。该复制构造函数将只复制 sizedata,进行浅拷贝,而不是深拷贝。这是一条通往灾难之路,因为当原始对象及其副本被销毁时,data 将被多次删除。

例如,如果您 运行 您在 Valgrind 下的原始代码,您将看到以下报告:

==9908== HEAP SUMMARY:
==9908==     in use at exit: 0 bytes in 0 blocks
==9908==   total heap usage: 3 allocs, 4 frees, 73,733 bytes allocated
==9908== 
==9908== All heap blocks were freed -- no leaks are possible
==9908== 
==9908== For counts of detected and suppressed errors, rerun with: -v
==9908== ERROR SUMMARY: 18 errors from 9 contexts (suppressed: 0 from 0)

如果你想Data是可复制的,你必须提供一个深复制构造函数来分配新的存储并将数据复制到其中。您问题中的那个看起来几乎 (*) 不错:

==9993== HEAP SUMMARY:
==9993==     in use at exit: 0 bytes in 0 blocks
==9993==   total heap usage: 4 allocs, 4 frees, 73,738 bytes allocated
==9993== 
==9993== All heap blocks were freed -- no leaks are possible
==9993== 
==9993== For counts of detected and suppressed errors, rerun with: -v
==9993== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

如果你根本不需要复制构造,你可以通过显式删除来禁用它的隐式生成:

Data(const Data&) = delete;

复制赋值运算符也是如此。 implicitly-defined one will not do what you want. Don't forget一下吧。

(*)注意other.data可以是nullptr,所以你在复制前have to check it

void* memcpy(void* dest, const void* src, std::size_t count);

If either dest or src is a null pointer, the behavior is undefined, even if count is zero.