【问题标题】:C++ : Pushing an object into a vectorC ++:将对象推入向量
【发布时间】:2018-04-07 22:56:51
【问题描述】:

来自 C 背景,我正在尝试自学 C++。我一直在尝试实现 UnionFind 数据结构。我有一堂课如下:-

class UnionFind
{
private:
    int count;
    UnionFind* parent;
    int val;

public:
    UnionFind(int _val) : count(1), parent(this), val(_val) {
        printf("UnionFind(): parent=%p this=%p\n", parent, this);
        }

    ~UnionFind() {
        printf("~UnionFind()\n");
    }

    int getcount()
    {
        if (parent == this)
            return count;
        return Find()->count;
    }

    int getval() { return val; }
    void setparent(UnionFind* _parent) { parent = _parent; }
    void setcount(int _count) { count = _count; }

    // Does a union of 2 sets
    void Union(UnionFind* u)
    {
        printf("Union(%d: this=%p, %d: u=%p)\n", val, this, u->getval(), u);
        UnionFind* this_parent = Find();
        UnionFind* u_parent = u->Find();
        printf("this_parent=%p u_parent=%p\n", this_parent, u_parent);

        if (this_parent == u_parent)
            return;

        if (this_parent->getcount() > u_parent->getcount()) {
            this_parent->setcount(this_parent->getcount() + u_parent->getcount());
            u_parent->setparent(this_parent);
        } else {
            u_parent->setcount(this_parent->getcount() + u_parent->getcount());
            this_parent->setparent(u_parent);
        }
    }

    // Returns the parent
    UnionFind* Find()
    {
        //printf("%d: Find(%p) = ", val, this);
        UnionFind* temp = parent;
        while (temp != temp->parent)
            temp = temp->parent;
        //printf("%p\n", temp);
        return temp;
    }
};

稍后我尝试用UnionFind 的一些实例填充向量,如下所示:-

vector<UnionFind> vecs;
for (int i = 0; i < N; i++)
    vecs.push_back(UnionFind(i));

运行此代码显示 UnionFind 对象都具有相同的地址。我怀疑(并确认)这是因为在循环的每次迭代中都会调用 UnionFind 的析构函数。

我的问题是:-

  • 我意识到我可以创建一个指向 UnionFind 对象的指针向量作为vector&lt;UnionFind*&gt; 来解决这个问题,但这真的是要走的路吗?我读到与 C 不同的是,在 C++ 中可能解决很多问题,而无需求助于 new/malloc 或 delete/free。解决这个问题的 C++ 方法是什么?

[编辑]

已纠正错字。

[编辑 2]

我正在尝试实现一个不相交集/联合查找数据结构。 "parent" 最初会指向自身,但随着联合的发生,父级可能会指向不同的对象。

[编辑 3]

已添加完整代码。

【问题讨论】:

  • Vector 对其内容进行了大量的移动和洗牌。这意味着您放入其中的任何内容都必须可以安全地复制和分配。这不是因为parent 会搞砸。查看Rules of Three Five, and Zero。如果不掌握这些规则,你就不能做重要的 C++。您还需要密切关注Iterator invalidation rules,否则将指向容器中的项目的指针存储在容器中只会造成痛苦。
  • 由于所有的洗牌和调整大小,正在发生很多破坏。但在vector 的内部存储调整大小之前,vecs[0] 的地址将始终相同,无论UnionFind 当前占用该插槽是什么。
  • 感谢您提供的链接和信息,您能否说明是否有办法在不使用新/删除的情况下解决此问题?
  • 您到底想解决什么问题?您是否要确保每个对象都有parent == this
  • 你需要多少parent?使用parent,如果您允许复制或移动对象,您就有大量的簿记工作。你最好使用指针,这样地址就不会改变,但是guard the pointer with a std::unique_ptr (vector&lt;unique_ptr&lt;UnionFind&gt;&gt;) 所以它的所有权被锁定并保证删除。

标签: c++ vector scope stl


【解决方案1】:

这里有一些影响,您误解了正在发生的事情。创建vector&lt;UnionFind *&gt; 并使用动态内存分配可以解决问题的症状,但不能解决根本原因。

首先,在循环的每次迭代中,

vecs.push_back(UnionFind(i));

构造一个临时的UnionFind 对象,将其副本存储在向量中,然后销毁该临时对象。虽然不能保证,但没有什么可以阻止所有具有相同地址的临时人员。

其次,当使用int 参数调用构造函数时,每个对象都将自己的地址存储在其parent 成员中。如果临时对象是在同一个地址创建的,那么它们都将具有parent 成员相等。另请注意,您的程序的输出将仅包含有关main() 创建的临时对象的信息。

第三,当向量的push_back() 附加它接收到的对象的副本时,它会复制该对象(通常使用复制构造函数)。您的代码尚未实现复制构造函数,因此编译器会自动生成一个。编译器生成的复制构造函数(和赋值运算符)按 VALUE 复制所有成员。因此,当复制UnionFind 对象以创建另一个对象时,新创建的对象将使其parent 成员指向原始对象。因此,假设循环中的每个对象都具有相同的地址,则每个 COPIED 对象都将具有相等的 parent 成员。

第四,由于您只实现了一个构造函数,因此您的输出仅包含有关在main() 中创建的临时对象的信息。您将看不到有关存储在向量中的那些临时对象的副本的任何信息。

同样的效果也可以看到,更简单的是用;

  UnionFind a(42);
  UnionFind b(a);

它正在创建两个对象(b 作为a 的副本),但您只会看到有关a 的信息正在输出(即有两个对象,但只打印关于其中一个的信息)。如果您还想查看有关 b 的信息,则需要实现一个复制构造函数来完成这项工作,例如;

UnionFind(const UnionFind &rhs) : count(rhs.count), parent(this), val(rhs.val)
{
    printf("UnionFind(): parent=%p this=%p\n", parent, this);
}

如果你实现了复制构造函数,通常还需要实现其他成员函数。查找“三规则” - 一个需要手工制作的复制构造函数的类,就像你的那样,通常也需要手工制作的赋值运算符和手工制作的析构函数。此外,我在上面的描述中过度简化了,因此(在 C++11 及更高版本中)查找“5 规则”,因为您可能还需要手动实现移动构造函数以正确跟踪对象。

然而,更一般地说,如果您想学习 C++,最好避免与 C 类比。在 C 中有效的技术在 C++ 中通常无效,反之亦然。学习 C++ I/O 而不是坚持使用 C I/O(您可以在 C++ 中使用 C I/O 并不意味着它在 C++ 中与在 C 中一样有效)。避免在 C++ 中使用 malloc()free(),因为它们不会与具有非平凡构造函数和析构函数的 C++ 类类型一起使用。事实上,在 C++ 中完全避免使用动态内存分配 - 在大多数情况下,有更安全的替代方案。

【讨论】:

    【解决方案2】:

    您的联合查找对象在向量中并非都具有相同的地址。您正在堆栈上构造一个对象,堆栈对象具有相同的地址。然后它正在被复制构造,并将副本放置在向量中。

    std::vector 中的 append 函数在哪里?

    错误:'std::vector >'中没有​​名为'append'的成员

    【讨论】:

    • 感谢您的信息,我已通过将 append 更改为 push_back 来纠正错字。您能否提供更多关于如何在不使用 new/delete 的情况下编写此代码的信息?
    • @learnlearnlearn 您可以做的是重载 = 运算符,将其设置为 parentthis。请注意,这是一种适用于此处的解决方法,但不一定是 = 运算符所期望的。也就是说,我的建议是放弃代表类的节点,而是有一个代表整个图/算法的类,将节点存储在两个向量中,代表节点数据和一个整数类型的索引作为父级而不是指针。毕竟这是一个程序算法。还将使改进的 UnionFind 的自动更新更容易。
    猜你喜欢
    • 1970-01-01
    • 2020-05-22
    • 2015-10-06
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-23
    相关资源
    最近更新 更多