6

我有一个遵循这种模式的课程:

class Foo
{
public:
    // Create a Foo whose value is absolute
    Foo(int x) : other_(0), a_(x)  {}

    // Create a Foo whose value is relative to another Foo
    Foo(Foo * other, int dx) : other_(other), a_(dx) {}

    // Get the value
    double x() const
    {
        if(other_)
            return other_->x() + a_;
        else
            return a_;
    }

private:
    Foo * other_;
    int a_;
};

这些Foo对象都归 a 拥有Bar

class Bar
{
public:
    ~Bar() { for(int i=0; i<foos_.size(); i++) delete foos_[i]; }

private:
    vector<Foo*> foos_;
};

当然,这是一个简单的例子来理解这个想法。我保证没有 s 的循环Foo,并且链接Foo的 s 都属于 的同一个实例Bar。到目前为止,一切都很好。要以 C++11 的方式做事,我会使用vector< unique_ptr<Foo> > foos_;in ,并作为构造函数的潜在参数Bar传递。foos_[i].get()Foo

有这样的交易:

这是一个GUI应用程序,用户可以交互地随意删除一些Foo。预期的行为是如果foo1被删除,并且foo2相对于foo1,则foo2现在变为“绝对”:

void Foo::convertToAbsolute() { a_ += other_->x(); other_ = 0; }

void usageScenario()
{
    Foo * foo1 = new Foo(42);      
    Foo * foo2 = new Foo(foo1, 42);
    // Here, foo1->x() = 42 and foo2->x() = 84

    foo1->setX(10);
    // Here, foo1->x() = 10 and foo2->x() = 52

    delete foo1;
    // Here, foo2->x() = 52
}

我知道可以使用原始指针,通过使用带有反向指针的 DAG 结构来做到这一点,因此Foo知道谁“依赖于他们”,并可以在删除之前通知他们(此处此处详述的可能解决方案)。

我的问题是:你会以同样的方式处理它吗?有没有办法使用标准 C++11 智能指针来避免显式反向指针,然后避免显式调用areRelativeToMe_[i]->convertToAbsolute();的析构函数Foo?我在想weak_ptr,本着以下精神:

class Foo { /* ... */ weak_ptr<Foo> other_; };

double Foo::x() const
{
    if(other_.isExpired())
        convertToAbsolute();

    // ...
}

但问题是convertToAbsolute()需要亲属Foo仍然存在。所以我需要一个非拥有的智能指针,它可以告诉“这个引用在逻辑上已过期”,但实际上延长了被引用对象的生命周期,直到不需要它为止。

它可以被看作是weak_ptr延长生命周期,直到它不与任何其他人共享weak_ptr

class Foo { /* ... */ extended_weak_ptr<Foo> other_; };

double Foo::x() const
{
    if(other_.isExpired())
    {
        convertToAbsolute();
        other_.reset(); // now the object is destructed,  unless other
                          // foos still have to release it
    }

    // ...
}

或者像shared_ptr拥有不同级别的所有权:

class Bar { /* ... */ vector< multilevel_shared_ptr<Foo> foos_; };

class Foo { /* ... */ multilevel_shared_ptr<Foo> other_; };

void Bar::createFoos()
{ 
    // Bar owns the Foo* with the highest level of ownership "Level1"

    // Creating an absolute Foo
    foos_.push_back( multilevel_unique_ptr<Foo>(new Foo(42), Level1) );

    // Creating a relative Foo 
    foos_.push_back( multilevel_unique_ptr<Foo>(new Foo(foos_[0],7), Level1) );
}

Foo::Foo(const multilevel_unique_ptr<Foo> & other, int dx) :
    other_( other, Level2 ),
   // Foo owns the Foo* with the lowest level of ownership "Level2"
    a_(dx) 
{
}

double Foo::x() const
{
    if(other_.noLevel1Owner()) // returns true if not shared 
                               // with any Level1 owner
    {
        convertToAbsolute();
        other_.reset(); // now the object is destructed, unless 
                        // shared with other Level2 owners
    }
    // ...
}

有什么想法吗?

4

5 回答 5

1

如果你有一个 Signal/Slot 框架,它提供了一个很好的去链接的地方。例如,使用 Qt 库,这些类可能看起来像:

class Foo : public QObject
{
Q_OBJECT
public:
    // Create a Foo whose value is absolute
    Foo(int x) : QObject(nullptr), other_(nullptr), a_(x) {}

    // Create a Foo whose value is relative to another Foo
    Foo(Foo * other, int dx) : QObject(nullptr) other_(other), a_(dx) {
        connect(other, SIGNAL(foo_dying()), this, SLOT(make_absolute()));
    }

    ~Foo() { emit foo_dying(); }

    // Get the value
    double x() const
    {
        if(other_)
            return other_->x() + a_;
        else
            return a_;
    }

signals:
    void foo_dying();

private slots:
    void make_absolute()
    {
        a_ += other_->x();
        other_ = nullptr;
    }

private:
    Foo * other_;
    int a_;
};
于 2013-09-13T22:13:42.090 回答
1

全部FooBar. 因此,所有删除都Foo发生在Bar方法中。所以我可能会在里面实现这个逻辑Bar

void Bar::remove(Foo* f)
{
    using namespace std::placeholders;
    assert(std::any_of(begin(foos_), end(foos_),
                       std::bind(std::equal_to<decltype(f)>(), f, _1));

    auto const& children = /* some code which determines which other Foo depend on f */;
    std::for_each(begin(children), end(children),
                  std::mem_fn(&Foo::convertToAbsolute));
    foos_.remove(f);

    delete f; // not needed if using smart ptrs
}

这将确保在调用其依赖项Foo时过期仍然存在。convertToAbsolute

计算children方式的选择取决于您。我可能会让每个人都Foo跟踪自己的孩子(循环非拥有指针),但您也可以在内部跟踪它Bar,或者按需搜索以foos_在需要时重新计算它。

于 2013-09-13T21:26:36.047 回答
1

有趣的问题。我猜你认为你可以添加一个指向“子”对象的指针。我不确定,智能指针在这里是否有帮助。我尝试使用以下代码来实现,std::weak_ptr<Foo>但您只能将其用于侦听器,other_而不能用于侦听器。

我的另一个想法是将责任留给更高的权力。您遇到的问题是您想在调用析构函数时进行更新。也许更好的方法是convertToAbsolute()从其他地方打电话。例如,如果您将 存储Foos在向量中并且用户在 UI 中单击删除,则您需要对象的索引才能删除,因此不妨将相邻项目更新为绝对值。

下面是一个使用Foo*.

#include <iostream>
#include <memory>
#include <vector>


class Foo
{
public:
    // Create a Foo whose value is absolute
    Foo(int x) : other_(nullptr), listener_(nullptr), a_(x)
    {}

    // Create a Foo whose value is relative to another Foo
    Foo(Foo* other, int dx) : 
    other_(other), listener_(nullptr), a_(dx) 
    {
        other->setListener(this);
    }

    ~Foo()
    {
        convertToAbsolute();
        if (listener_)
            listener_->other_ = nullptr;
    }

    // Get the value
    double x() const
    {
        if(other_)
            return other_->x() + a_;
        else
            return a_;
    }

    void setX(int i)
    {
        a_ = i;
    }

    void convertToAbsolute()
    {
        if (listener_)
            listener_->a_ += a_;
    }

    void setListener(Foo* listener)
    {
        listener_ = listener;
    }

private:
    Foo* other_;
    Foo* listener_;
    int a_;
};


void printFoos(const std::vector<std::shared_ptr<Foo>>& foos)
{
    std::cout << "Printing foos:\n";
    for(const auto& f : foos)
        std::cout << '\t' << f->x() << '\n';
}

int main(int argc, const char** argv)
{
    std::vector<std::shared_ptr<Foo>> foos;
    try
    {
        auto foo1 = std::make_shared<Foo>(42);
        auto foo2 = std::make_shared<Foo>(foo1.get(), 42);

        foos.emplace_back(foo1);
        foos.emplace_back(foo2);
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << '\n';
    }

    // Here, foo1->x() = 42 and foo2->x() = 84
    printFoos(foos);

    foos[0]->setX(10);
    // Here, foo1->x() = 10 and foo2->x() = 52
    printFoos(foos);

    foos.erase(foos.begin());
    // Here, foo2->x() = 52
    printFoos(foos);

    return 0;
}
于 2013-09-12T23:14:17.120 回答
1

即使有多个其他从属对象,您也可以使用双链接方法。您只需要将同一对象的依赖项链接在一起:

class Foo {
public:
  explicit Foo(double x)
  : v(x), foot(nullptr), next(nullptr), dept(nullptr) {}

  // construct as relative object;  complexity O(1)
  Foo(Foo*f, double x)
  : v(x), foot(f), dept(nullptr)
  { foot->add_dept(this); }

  // destruct;  complexity  O(n_dept) + O(foot->n_dept)
  //                        O(1)  if !destroy_carefully
  ~Foo()
  {
    if(destroy_carefully) {
      for(Foo*p=dept; p;) {
        Foo*n=p->next;
        p->unroot();
        p=n;
      }
      if(foot) foot->remove_dept(this);
    }
  }

  double x() const
  { return foot? foot->x() + v : v; }

private:

  double v;   // my position relative to foot if non-null
  Foo*foot;   // my foot point
  Foo*next;   // next object with same foot point as me
  Foo*dept;   // first object with me as foot point

  // change to un-rooted;  complexity: O(1)
  void unroot()
  { v+=foot->x(); foot=nullptr; next=nullptr; }

  // add d to the linked list of dependents;  complexity O(1)
  void add_dept(const Foo*d)
  { d->next=dept; dept=d; }

  // remove d from the linked list of dependents ; complexity O(n_dept)
  void remove_dept(const Foo*d)
  {
    for(Foo*p=dept; p; p=p->next)
      if(p==d) { p=d->next; break; }
  }
  static bool destroy_carefully;

};
bool Foo::destroy_carefully = true;

在这里,设置Foo::destroy_carefully=false允许您删除所有剩余的对象,而无需解开相互引用(这可能很昂贵)。

于 2013-09-12T22:41:43.247 回答
0

这可能是使用反向指针实现目标的最简单方法。您可以根据您的复杂性要求使用您想要的容器(例如,集合、哈希表、向量、链表等)。Walter 提出了一种更复杂但更有效的方法。

class Foo
{
public:
    // Create a Foo whose value is absolute
    Foo(int x) : other_(0), a_(x)  {}

    // Create a Foo whose value is relative to another Foo
    Foo(Foo * other, int dx) : other_(other), a_(dx)
    {
        other->areRelativeToMe_.insert(this);
    }

    // Get the value
    double x() const
    {
        if(other_)
            return other_->x() + a_;
        else
            return a_;
    }

    // delete the Foo
    Foo::~Foo()
    {
        // Inform the one I depend on, if any, that I'm getting destroyed
        if(other_)
            other_->areRelativeToMe_.remove(this);

        // Inform people that depends on me that I'm getting destructed
        for(int i=0; i<areRelativeToMe_.size(); i++)
            areRelativeToMe_[i]->convertToAbsolute();
    }

private:
    Foo * other_;
    int a_;
    Container<Foo*> areRelativeToMe_; // must provide insert(Foo*) 
                                      //          and remove(Foo*)

    // Convert to absolute
    void convertToAbsolute()
    {
        a_ += other_->x(); 
        other_ = 0; 
    }
};
于 2013-09-12T23:30:58.620 回答