C ++向量,列表,指针和混乱

时间:2014-05-15 16:20:41

标签: c++ pointers data-structures

我有一个Entity类,其中包含特定类型实体的各种子类。子类的对象彼此之间具有各种关系,因此我使用Entity指针和static_cast来描述这些关系,并允许实体访问与其有关系的其他实体的信息。

然而,我发现使用原始指针是很多困难的来源。难以调试错误。例如,我浪费了几个小时才意识到我在将对象复制到向量之前指向对象,这使得指针无效。

我正在使用矢量和列表&像这样的集合来管理我的所有内存 - 我真的试图避免担心内存管理和低级别问题。

Say Cow是Entity的子类,我有一个Cows(或Cows的一些数据结构)的向量。如果需要调整大小或其他内容,此向量可能会在内存中移动。我真的不想关心这些细节,但我知道它们可能会使我的指针无效。因此,如果一只猪与一头母牛有关系并且牛的矢量被调整大小,那么猪就会失去其朋友在记忆中所处位置的轨迹。有一个危险的错误指针。

以下是我的问题,虽然我认为有经验的人可能只是通过阅读情况来帮助我...

  1. 我应该尝试将某些东西放入我的Entity对象的构造函数/析构函数中,以便它们在删除时自动使其他实体与它们之间的关系无效,或者如果它们被移动则更新内存位置?如果这是一个很好的方法,那么对语义的任何帮助都会很有用。我一直在编写我的所有类,没有构造函数或析构函数,因此它们非常容易在诸如向量之类的东西中使用。
  2. 是更好的选择,只是跟踪容器和&每当我知道容器要移动时手动更新指针?所以策略就是遍历任何可能在移动后立即移动的容器并立即更新所有指针。

3 个答案:

答案 0 :(得分:3)

据我所知,你正在做这样的事情:

cow florence, buttercup;
std::vector<cow> v = { florence, buttercup };
cow* buttercup_ptr = &v[1];

// do something involving the cow*

你的buttercup_ptr正在失效,因为,例如。 florence已从矢量中移除。

可以通过使用智能指针解决此问题,如下所示:

std::shared_ptr<cow> florence = std::make_shared<cow>();
std::vector<std::shared_ptr<cow>> v;
v.push_back(florence);

你现在可以自由地分享florence ...关于矢量如何被改组的问题,它仍然有效。

如果你想让florence在向量中弹出时被摧毁,那就会出现一个小问题:任何持有shared_ptr副本的人都会阻止此cow实例被清理干净。您可以使用weak_ptr来避免这种情况。

std::weak_ptr<cow> florence_ref = florence;

要使用weak_ptr,请在其上调用lock()将其转换为完整shared_ptr。如果底层的cow实例已经被销毁,因为它没有强引用,那么抛出异常(当然,你也可以在expired()之前调用lock()来检查它!)

另一种可能性(正如我在原始问题的评论中所建议的)是使用指向容纳cow实例的容器的迭代器。这有点陌生,但迭代器在许多方面都非常像指针。你必须小心选择容器,以确保你的迭代器不会失效(这意味着你不能使用vector来实现这个目的);

std::set<cow> s;
cow florence;
auto iter = s.insert(florence);

// do something with iter, and not worry if other cows are added to
// or removed from s

我认为我不一定会建议这个选项,但我相信它会起作用。使用智能指针是向未来维护程序员展示您的意图的更好方法,并且具有获取悬挂引用更加困难的优势。

答案 1 :(得分:2)

你对指针有误解。指针(为了讨论!)只是一个地址。为什么复制地址会使任何内容失效?假设您的对象是在地址12345的内存中创建的。因此,new会为您提供一个值为12345的指针:

Entity *entity = new Cow; // object created at address 12345
// entity now has value 12345

稍后,当您将该指针复制到向量(或任何其他标准容器)中时,除了值12345之外,不会复制任何内容。

std::vector<Entity *> entities;
entities.push_back(entity);
// entities[0] now has value 12345, entity does not change and remains 12345

事实上,与std::vector<int>相比,这方面没有区别:

int i = 12345;
std::vector<int> ints;
ints.push_back(i);
// ints[0] now has value 12345, i does not change and remains 12345

复制向量时,里面的值不会改变:

std::vector<int> ints2 = ints;
std::vector<Entity *> entities2 = entities;
// i, ints[0] and ints[0] are 12345
// entity, entities[0] and entities[0] are 12345
  

因此,如果猪与母牛有关系,那么母牛的载体就是   调整大小后,猪会失去其朋友在记忆中的位置   &安培;有一个危险的错误指针。

这种担心是没有根据的。猪不会 失去对其朋友的追踪。

危险的是保持指向已删除对象的指针:

delete entity;
entities[0]->doSometing(); // undefined behaviour, may crash

如何解决这个问题是C ++中成千上万个问题,网站,博客和书籍的主题。智能指针可能对您有所帮助,但您应该首先真正理解所有权的概念。

答案 2 :(得分:2)

我的两分钱。下面的代码是一个工作示例(C ++ 11,用g ++ 4.8.2翻译),带有智能指针和工厂函数create。应该是非常安全的。 (如果你真的想要,你总是可以在C ++中销毁一些东西。)

#include <memory>
#include <vector>
#include <algorithm>
#include <iostream>
#include <sstream>

class Entity;


typedef std::shared_ptr<Entity> ptr_t;
typedef std::vector<ptr_t> animals_t;

class Entity {
protected:
    Entity() {};
public:
    template<class E, typename... Arglist>
    friend ptr_t create(Arglist... args) {
        return ptr_t(new E(args...));
    }

    animals_t my_friends;

    virtual std::string what()=0;
};

class Cow : public Entity {
    double m_milk;
    Cow(double milk) :
        Entity(),
        m_milk(milk)
    {}
public:
    friend ptr_t create<Cow>(double);

    std::string what() {
        std::ostringstream os;
        os << "I am a cow.\nMy mother gave " << m_milk << " liters.";
        return os.str();
    }
};

class Pig : public Entity {
    Pig() : Entity() {}
public:
    friend ptr_t create<Pig>();

    std::string what() {
        return "I am a pig.";
    }
};

int main() {
    animals_t animals;

    animals.push_back(create<Cow>(1000.0));
    animals.push_back(create<Pig>());

    animals[0]->my_friends.push_back(animals[1]);

    std::for_each(animals.begin(),animals.end(),[](ptr_t v){
            std::cout << '\n' << v->what();
            std::cout << "\nMy friends are:";
            std::for_each(v->my_friends.begin(),v->my_friends.end(),[](ptr_t f){
                    std::cout << '\n' << f->what();
                });
            std::cout << '\n';
        });
}

/*
    Local Variables:
    compile-command: "g++ -std=c++11 test.cc -o a.exe && ./a.exe"
    End:
 */

通过将Entity构造函数声明为受保护的构造函数,强制必须通过工厂函数create创建Entity类型的派生对象。

工厂确保对象始终进入智能指针(在我们的例子中为std::shared_ptr)。

它被写为模板,使其可以在子类中重用。更确切地说,它是一个variadic template,允许构造函数在派生类中包含任意数量的参数。在程序的修改版本中,将母牛的产奶量作为构造函数参数传递给新创建的奶牛。