首页 > 解决方案 > C++ successfully copying dynamically allocated obj without copy ctor?

问题描述

I have a class with pointer to char. I am wondering how is it possible that copy construction is possible without explicitly defining copy constructor?

I assume because the object was already allocated, but if so, then why would someone need copy constructors in the first place?

#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;
}

Output:

data1
data1

Shouldn't the copy constructor look like that? I am often seeing such examples. But since the default constructor already did that, then when do I actually need to define copy ctor?

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

By the way, I realize the code introduces some maul practices (e.g. using new instead of smart ptrs, not using string in the first place, etc. - but it's just an exercise).

标签: c++oopcopy-constructor

解决方案


从 C++11 开始,如果类具有用户定义的析构函数,则不推荐生成隐式定义的复制构造函数(这是有充分理由的),但仍会生成它。该复制构造函数将仅复制sizeand data,进行浅复制,而不是深复制。这是一条通往灾难的道路,因为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;

复制赋值运算符也是如此。隐式定义的不会做你想做的事。不要忘记它。

(*) 注意other.datacan nullptr,所以在复制之前必须检查:

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

如果其中一个destsrc为空指针,则行为未定义,即使count为零也是如此。


推荐阅读