【问题标题】:Recommended way to realise bidirectional visibility in C++在 C++ 中实现双向可见性的推荐方法
【发布时间】:2018-02-20 20:21:18
【问题描述】:

我想知道在我认为 C++ 14 中类实例及其成员之间的双向可见性方面,这种推荐的方法是什么。

举个例子,假设我想编写一个类 World,其中填充了不同种类的人。所以 World 拥有一个 std::vector>。 Person 应该是一个抽象基类,在 PersonOnePersonTwo 中实现了纯虚函数。现在我想让人员实例能够访问他们的世界,包括其他人。

我的第一种方法是将世界作为 reference 传递给世界构造函数中的 this。但是,我不确定这是否会给我带来麻烦。理想情况下,我希望这个自包含的,即包括人在内的世界应该在世界的构造函数中完全初始化,并且应该可以在不单独调整人的属性的情况下复制构建一个世界。智能指针似乎不是一个可行的替代方案,因为我无法在世界的构造函数中为此创建 shared_ptr 并将其传递给他们的成员。

class Person{
    public:
    World &world;

    Person(World &_w):world(_w){};
};

class PersonOne: public Person{

    public: 
    PersonOne(World &_w):Person(_w);
};

class PersonTwo: public Person{

    public: 
    PersonTwo(World &_w):Person(_w){};

};

class World{
    public:
    std::vector<std::unique_ptr<Person>> people;

    World(){
        people.push_back(std::make_unique<PersonOne>(*this));
        people.push_back(std::make_unique<PersonTwo>(*this));
    }; 
};

【问题讨论】:

  • 问题可能是当你从World继承另一个类时。
  • 使用std::reference_wrapper&lt;World&gt; 作为Person 的成员。您可以将this 传递给Person 的构造函数,然后使用std::ref 初始化person 中的std::reference_wrapper&lt;World&gt; 成员。
  • @cantordust:这是如何改进的?到目前为止,我认为 std::reference_wrapper 主要用于使用引用,例如在容器中,所以我在这里看不到可以使用普通参考的用途。
  • @Chris 你可以重新分配一个std::reference_wrapper,但是你不能用一个简单的引用来做到这一点。这反过来又允许您复制您的Worlds。此外,您可以通过简单地将World&amp; 传递给Person 的构造函数来创建std::reference_wrapper&lt;World&gt;。正如您所提到的,您不能像那样创建std::shared_ptr&lt;World&gt;

标签: inheritance constructor reference c++14 smart-pointers


【解决方案1】:

在这种情况下复制很棘手,因为您必须始终确保Persons 引用正确的World。一种选择是确保您在World 中有自定义复制构造函数和operator=,您可以在其中调用函数来更新您的人口。像这样的:

#include <iostream>
#include <vector>
#include <string>
#include <functional>

using uint = unsigned int;

class World;
using WorldRef = std::reference_wrapper<World>;

class Person
{
    WorldRef world;
    std::string name;
    friend class World;
public:

    Person(World& _world,
           const std::string& _name)
        :
          world(_world),
          name(_name)
    {}
};

class World
{
    uint year;
    std::string name;
    std::vector<Person> population;
    friend class Person;

    void init()
    {
        for (auto& p : population)
        {
            /// Note: This is necessary, otherwise this world's
            /// inhabitants will refer to the wrong world.
            /// Call this function from each constructor and
            /// assignment operator.
            p.world = std::ref(*this);
        }
    }

public:

    World(const uint _year,
          const World& _other,
          const std::string& _name)
        :
          year(_year),
          population(_other.population),
          name(_name)
    {
        init();
        std::cout << "It is now " << year << "\n"
                  << "Melon Tusk has built a rocket allowing cheap interplanetary travel.\n";
        for (auto& p : population)
        {
            std::cout << p.name << " has moved to " << p.world.get().name << "\n";
        }
    }

    World(const uint _year,
          const std::string& _name,
          const uint _pop_size)
        :
          year(_year),
          name(_name),
          population(_pop_size, {*this, "Anonymous " + _name + "ling"})
    {
        init();
        std::cout << "It is " << year << "\n";
        std::cout << name << " has " << population.size() << " inhabitants:\n";
        for (const auto& p : population)
        {
            std::cout << p.name << "\n";
        }
    }

    void operator = (const World& _other)
    {
        population = _other.population;
        init();
    }

    void set_name(const std::string& _name)
    {
        name = _name;
    }

    std::string get_name() const
    {
        return name;
    }

    void add_person(const std::string& _name)
    {
        population.emplace_back(*this, _name);

        std::cout <<_name << " was born on " << name << "\n";
    }

    uint population_size() const
    {
        return population.size();
    }
};

int main()
{
    World earth(2018, "Earth", 5);
    World mars(2025, earth, "Mars");

    mars.add_person("Alice");
    mars.add_person("Bob");
    mars.add_person("Carol");

    World europa = mars;
    europa.set_name("Europa");

    std::cout << "Everyone has moved on to " << europa.get_name() << "\n";

    europa.add_person("David");

    std::cout << europa.get_name() << " has a population of " << europa.population_size() << "\n"
              << "A war is brewing over scant resources.\n"
              << "Such is the nature of humanity.\n";

    return 0;
}

打印出来

It is 2018
Earth has 5 inhabitants:
Anonymous Earthling
Anonymous Earthling
Anonymous Earthling
Anonymous Earthling
Anonymous Earthling
It is now 2025
Melon Tusk has built a rocket allowing cheap interplanetary travel.
Anonymous Earthling has moved to Mars
Anonymous Earthling has moved to Mars
Anonymous Earthling has moved to Mars
Anonymous Earthling has moved to Mars
Anonymous Earthling has moved to Mars
Alice was born on Mars
Bob was born on Mars
Carol was born on Mars
Everyone has moved on to Europa
David was born on Europa
Europa has a population of 9
A war is brewing over scant resources.
Such is the nature of humanity.

注意我们如何在每个构造函数中调用init()operator=。根据您的需要,可能有一种更简洁的方法。

附:我不确定从逻辑角度复制 World 对象意味着什么,因为您最终会得到Persons 的克隆。也许这正是您想要的,但我只是想提一下。

【讨论】:

  • 感谢指出构造函数问题;我想到了这一点,但是将其作为参考可能对其他人有好处。是的,计划是克隆不同的世界,包括里面的人来测试反事实。
  • @Chris 听起来像是 Total Recall :)
猜你喜欢
  • 2021-05-02
  • 2017-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-16
  • 2015-05-16
  • 1970-01-01
  • 2011-10-18
相关资源
最近更新 更多