std::vector 在堆上分配了元素 - 我需要 5 法则吗?

std::vector with elements allocated on the heap - do I need rule of 5?

提问人:user_185051 提问时间:9/8/2020 最后编辑:user_185051 更新时间:9/8/2020 访问量:198

问:

如果我有一个班级,成员是这样的:

class MyClass {
public:
    void set_my_vector() {
        for (int ind = 0; ind < 3; ++ind) {
            my_vector.push_back(new MyStruct(i, i*2));
        }
    }
private:
    struct MyStruct {
        int num_a;
        int num_b;
        MyStruct(int i, int j) : num_a(i), num_b(j) {}
    };
    std::vector<MyStruct*> my_vector;
};

我是否需要编写五法则函数,或者 std::vector 会负责深度复制和删除堆上分配的元素?

编辑: 下面的代码使用默认的复制构造函数,因此我假设my_class1对象复制到my_class2对象后,my_class1.my_vector 和 my_class2.my_vector 的元素将是相同的,因为复制了 MyStruct 指针,而不是数据本身。但是,输出显示它们并不相同。您可以在此处运行代码:https://onlinegdb.com/S1pK9YE4v

#include <iostream>
#include <vector>

class MyClass {
public:    
    void fill_my_vector(int i, int j) {
        my_vector.clear();
        for (int ind = 0; ind < 3; ++ind) {
            my_vector.push_back(new MyStruct(i, j));
        }
    }

    void print () {
        for (int ind = 0; ind < 3; ++ind) {
            std::cout << my_vector[ind]->int1 << ", " << my_vector[ind]->int2 << std::endl;
        }
        std::cout << std::endl;
    }
private:
    struct MyStruct {
        MyStruct (int i, int j) :
        int1(i), int2(j)
        {}
    
        int int1;
        int int2;
    };

    std::vector<MyStruct*> my_vector;
};

int main()
{
    MyClass my_class1;
    my_class1.fill_my_vector(42, 43);

    std::cout << "my_class1: " << std::endl;
    my_class1.print();

    MyClass my_class2 = my_class1;
    my_class2.fill_my_vector(12, 13);

    std::cout << "my_class2: " << std::endl;
    my_class2.print();

    std::cout << "my_class1: " << std::endl;
    my_class1.print();

}

编辑2:我知道智能指针。我特别感兴趣的是,如果我使用原始指针会发生什么。

C++ 析构函数 五法则

评论

0赞 user4581301 9/8/2020
正如你所推测的,并且你最近的编辑已经确认,a 为你复制了它的元素。如果这些元素恰好是指向其他对象的指针,则由您自己负责。地址将被复制,因为它们是元素的值,而不是指向对象。是否需要三/五规则保护取决于谁拥有这些指向的对象。在这种情况下,看起来您可能至少需要三法则。vector
0赞 user_185051 9/8/2020
但是,如果只复制指针,那么在 和 之后,我假设它也会有元素 (12, 13) 。但它有元素(42、43),为什么?MyClass my_class2 = my_class1;my_class2.fill_my_vector(12, 13);my_class1. my_vector
1赞 user4581301 9/8/2020
您已将指针与它们指向的对象混为一谈。指针是一个对象,它恰好保存另一个对象的地址。此处已复制指针,最初两个副本都指向同一位置。您可以将其中一个指针指向其他位置,并有两个指向不同对象的指针,但在本例中,'被清除了 - 不再有任何指针 - 并且充满了指向全新对象的全新指针。两者之间不再有任何联系。my_class2vectormy_class
1赞 user4581301 9/8/2020
建议:坐下来画一些图片,画一些谁在指着什么以及何时指向什么的图片,以帮助自己想象正在发生的事情。
0赞 user_185051 9/8/2020
哦,我明白了,有道理。谢谢你,@user4581301。

答:

2赞 Tanveer Badar 9/8/2020 #1

您需要实现复制构造函数、复制赋值和析构函数。

此外,请考虑将向量声明从

std::vector<MyStruct*> my_vector;

std::vector<std::unique_ptr<MyStruct>> my_vector;

这样它实际上就正确地拥有堆分配的对象。执行此更改将帮助您不编写析构函数。

评论

0赞 user_185051 9/8/2020
你能根据我对原始帖子的编辑添加更多细节吗?如果我使用例如默认复制构造函数,究竟会发生什么?
1赞 ivan.ukr 9/8/2020 #2

不,不负责指针存储的对象的深度复制。解决此问题的可能性很小:std::vector

  • 按值存储。MyStruct
  • 商店。std::unique_ptr<MyStruct>
  • 商店。std::shared_ptr<MyStruct>

请注意,由于仅包含原始类型的字段,因此不需要复制构造函数、赋值运算符和析构函数,否则您必须实现它们,因此编译器将自动生成的默认实现就足够了。MyStruct

评论

0赞 user_185051 9/9/2020
如果 不会处理深度复制,那么为什么“编译器会自动生成的默认实现就足够好了”?我假设默认实现将对矢量的元素进行浅层复制,不是吗?std::vector
1赞 ivan.ukr 9/9/2020
复制构造函数、赋值和析构函数仅在类中保持一些非平凡状态时才是必需的,例如,如果您有指向某个对象的指针,因此在从 this 或赋值创建新对象时必须复制该对象。int、double 等原始数值类型的成员变量可以逐位复制,这是编译器默认做的,所以就足够了。