C++ class 改变“*this”指的是什么

C++ class changing what "*this" refers to

我有一个 class "foo",其中包含一个成员向量,其中包含 "foo" 类型的元素。 class 有一个名为 "make" 的方法,它创建 "foo" 个对象并将它们附加到向量中。我还提供了一种导航 "foo" 对象的方法,它是名为 "get" 的 "foo" 向量元素。

我想重新分配 "this" 在任何 "foo" 对象上引用的内容,以指向其向量中的 foo 对象。我想为代码提供此功能,以便更有效地导航和跟踪 foo 对象,而不必将引用绑定到特定的 "foo" 成员。我正在尝试通过一种名为 "setAsNode" 的方法来做到这一点,该方法重新分配“*this”是什么。

这是我模拟的一些示例代码,我相信它能表达我的观点:

struct foo{
    foo(const std::string &r):s(r){}
    foo& make(const std::string &r){children.push_back(test(r)); children.back().originalNode = originalNode; return children.back();}
    foo& setAsNode(){*originalNode = *this; return *originalNode;}
    foo& get(int i){return children.at(i);}
private:
    std::vector<foo> children;
    std::string s = "someData";
    foo *originalNode = this;
};

以及它如何运作的实际例子:

foo f1("test1");
f1.make("test2").make("test3");//pushes a new foo element back to the vector in f1 and pushes a new foo element back to it's vector
f1.get(0).get(0);//refers to the deepest nested object we just made above, cumbersome to type especially if the nesting is deep
f1.get(0).get(0).setAsNode();//this is where the error occurs, the desired effect is to make f1 the same object as denoted by f1.get(0).get(0);

foo& f2 = f1.get(0).get(0);//f2 would be equivalent to what I want the above code to reset f1 to be (or at least how I'd like it to act)

我意识到我可能正在实施一些非常糟糕的编程实践,例如使用引用而不是指针来返回 "foo" 对象,但老实说我不知道​​如何正确构建这样的程序,或者该程序的最佳实践版本如何 look/work。谁能告诉我 "right" 做事的方法,谁就能加分。

回到真正的问题:我如何制作这样的东西,特别是 "setAsNode" 方法,真正起作用?另外,为什么我的示例中的代码不起作用?请注意,它编译得很好,只是在 运行 上崩溃了。

在您的示例中,对 foo.get(0).get(0).setAsNode() 的调用将尝试将 foo.get(0).get(0) 的值复制到 foo。在此过程中,foo.children 将被分配一个新值,导致 vector 清除其先前的元素,从而导致 foo.get(0).get(0) 被破坏。这意味着 this 已经被销毁,指针无法使用。但是,这是在我们当前使用 this 的赋值操作期间发生的。要解决这个问题,您必须确保被复制的值持续存在足够长的时间才能被复制。直观的解决方案可能是在分配之前复制要分配的值。

foo& setAsNode() { 
    auto this_copy = *this;
    *originalNode = std::move(this_copy); 
    return *originalNode; 
}

这会起作用,你仍然要小心,不要在赋值给 *originalNode 之后使用 this。另一种解决方案是在执行分配之前控制 originalNode 的子向量。在此版本中,this 在方法 returns 之前一直有效,但如果以下赋值抛出异常,您的树将处于无效状态。

foo& setAsNode() { 
    auto original_vect = std::move(originalNode->children);
    *originalNode = *this;
    return *originalNode; 
}

总而言之,我会对要求对象自杀的设计持谨慎态度。这意味着对象控制自己的所有权,或者所有权责任是周期性的。

C++ 方法(可以说是唯一正确的方法)是分离关注点

foo 不是(或不应该是)foo-finder。它应该做 foo-things,而不是 foo-navigation 的事情。

创建一个新的 class 作为 foo 的游标或迭代器。

这是一个稍微扩展的版本,其中 foo_cursor 记住了它在 foo 堆栈中的旅程。可能对你的问题有点矫枉过正,但它展示了将 foo-navigation 逻辑与 foo-implementation 逻辑分开的原则。

你做的越多,你的程序就越容易编写、调试和维护。

#include <utility>
#include <string>
#include <vector>
#include <stack>
#include <stdexcept>
#include <iostream>


struct foo{
    foo(const std::string &r)
        : children()
        , s(r)
    {}

    foo& make(const std::string &r)
    {
        children.emplace_back(r);
        return children.back();
    }

    foo& get(int i)
    {
        return children.at(i);
    }

    void print() const {
        std::cout << s << std::endl;
    }


private:

    std::vector<foo> children;
    std::string s = "someData";
};

struct foo_cursor
{
    foo_cursor(foo& f)
        : current_(std::addressof(f))
    {}

    foo_cursor& down(int i)
    {
        history_.push(current_);
        current_ = std::addressof(current_->get(i));
        return *this;
    }

    foo_cursor& up() {
        if (history_.empty()) {
            throw std::logic_error("went up too far");
        }
        else {
            current_ = history_.top();
            history_.pop();
        }
        return *this;
    }

    foo* operator->() const {
        return current_;
    }

private:
    foo* current_;
    std::stack<foo*> history_;
};


int main()
{
    foo f("a");
    f.make("b").make("c");

    auto fc = foo_cursor(f);
    fc.down(0).down(0)->print();
    fc.up()->print();
    fc.up()->print();
}

预期输出:

c
b
a