【问题标题】:How to use a 2d vector of pointers如何使用指针的二维向量
【发布时间】:2012-11-05 17:45:30
【问题描述】:

实现高效二维向量的正确方法是什么?我需要将一组 Item 对象存储在 2d 集合中,这样可以快速迭代(最重要)并且可以快速查找元素。

我有一个 2d 指针向量,声明如下:

std::vector<std::vector<Item*>> * items;

在构造函数中,我将其实例化如下:

items = new std::vector<std::vector<Item*>>();
items->resize(10, std::vector<Item*>(10, new Item()));

我如何(正确)实现访问项目的方法?例如:

items[3][4] = new Item();

AddItem(Item *& item, int x, int y)
{
     items[x][y] = item;
}

我使用指针的原因是为了获得更好的性能,这样我就可以通过引用传递东西。

如果有更好的方法来解决这个问题,请解释一下,但是我仍然对如何正确使用向量感兴趣。

编辑:为澄清起见,这是一个简单游戏中用于库存管理的类的一部分。设置的 10x10 向量表示作为设置大小的库存网格。 Item 类包含项目类型、资源管理器中指向图像的指针、堆栈大小等。

我的指针使用是为了提高性能,因为这个类是迭代的,并使用图像指针在每一帧渲染整个库存。

【问题讨论】:

  • 您想使用指针通过引用来传递东西,而不是使用 C++ 中已有的引用功能?声明变量/参数时,请参阅 &amp; 运算符。此外,您应该使用智能指针而不是原始指针。第三,您是否测量过这将是一个瓶颈,或者您只是在进行可能不需要的过早优化并且不必要地使代码复杂化?
  • 我对 c++ 还是很陌生,我会仔细看看 & 运算符,我的印象是我需要将东西存储为指针。这是学习练习的一部分,所以我没有看过智能指针,对效率很感兴趣。
  • Way 这段代码中的指针太多了。不要使用原始指针。拥有原始指针和硬指针,因此会导致错误(例如,在您的代码中,二维数组的每个元素都指向相同的 Item),并且也比正常方式 .不要那样做。
  • 既然是二维向量,我觉得应该叫它矩阵。您知道编译时的大小 (10),因此您可以使用 boost 数组(如果您使用的是 C++11,则应考虑使用 std::array)而不是向量。由于数组在栈上分配元素,在堆上分配向量,因此与 stl 向量相比,数组可以具有更好的性能。
  • 首先,不要担心性能问题,直到你找到了可以工作的东西。其次,动态分配可能相对昂贵,因此动态分配所有内容通常对性能不是很好。第三,您可以轻松引用非动态分配的事物。 (您确实必须了解生命周期问题,但动态分配也是如此。)

标签: c++ pointers stl vector


【解决方案1】:

您似乎事先知道矩阵的大小,并且该矩阵是平方的。虽然vector&lt;&gt; 很好,但在这种情况下您也可以使用原生向量。

Item **m = new Item*[ n * n ];

如果你想访问位置r,c,那么你只需要将r乘以n,然后加上c:

pos = ( r * n ) + c;

所以,如果要访问位置 1、2、n = 5,那么:

pos = ( 1 * 5 ) + 2;
Item * it =  m[ pos ];

另外,除了使用普通指针,您还可以使用智能指针,例如auto_ptr(已过时)和unique_ptr,它们或多或少相似:一旦它们被销毁,它们就会销毁它们指向的对象.

auto_ptr<Item> m = new auto_ptr<Item>[ n * n ];

唯一的缺点是现在你需要调用get()才能获取指针。

pos = ( 1 * 5 ) + 2;
Item * it =  m[ pos ].get();

这里有一个总结了所有这些的课程:

class ItemsSquaredMatrix {
public:
    ItemsSquaredMatrix(unsigned int i): size( i )
        { m = new std::auto_ptr<Item>[ size * size ]; }
    ~ItemsSquaredMatrix()
        { delete[] m; }

    Item * get(unsigned int row, unsigned int col)
        { return m[ translate( row, col ) ].get(); }
    const Item * get(unsigned int row, unsigned int col) const
        { return m[ translate( row, col ) ].get(); }

    void set(unsigned int row, unsigned int col, Item * it)
        { m[ translate( row, col ) ].reset( it ); }

    unsigned int translate(unsigned int row, unsigned int col) const
        { return ( ( row * size ) + col ); }

private:
    unsigned int size;
    std::auto_ptr<Item> * m;
};

现在您只需创建类Item。但是,如果您创建了一个特定的类,那么您必须为每个新数据复制 ItemsSquaredMatrix。在 C++ 中,对此有一个特定的解决方案,涉及在 模板 中转换上述类(提示:vector&lt;&gt; 是一个模板)。由于您是初学者,将Item 作为抽象类会更简单:

class Item {
public:
    // more things...
    virtual std::string toString() const = 0;
};

并派生您将从它们创建的所有数据类。不过记得做演员表……

如您所见,有很多未解决的问题,而且随着您不断揭示问题,还会提出更多问题。享受吧!

希望这会有所帮助。

【讨论】:

  • 谢谢,我已经编辑了我的原始帖子,试图澄清一切。使用计算索引似乎很流行,由于索引计算,我认为它不太受欢迎。我将研究 unique_ptr,直到现在我一直认为我应该自己管理所有内容的原始指针,我现在认为这是一种过时的做事方式?
  • 这并不是说它是过时的,它是一种寻找问题的方法,当你有一些机制时,虽然简单且不如垃圾收集器强大(而且不消耗资源),但它们自动纠正许多与内存管理相关的潜在问题(主要是与忘记“删除”内存相关的内存泄漏)。这是基于随着对象的创建和销毁而自动调用的构造函数/析构函数方法。基于此的更一般的模式是 RAII:en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
【解决方案2】:

对于数值工作,您希望将数据尽可能本地存储在内存中。例如,如果您要创建 nm 矩阵,您可能会想将其定义为

vector<vector<double>> mat(n, vector<double>(m));

这种方法有严重的缺点。首先,它不适用于任何适当的矩阵库,例如BLASLAPACK,它们希望数据在内存中是连续的。其次,即使这样做,也会导致内存中出现大量随机访问和指针间接,这会降低矩阵运算的性能。相反,您需要一个连续的内存块n*m 个大小的项目。

vector<double> mat(n*m);

但您不会真的想为此使用vector,因为您需要手动将一维索引转换为二维索引。在C++ 中有一些库可以为您执行此操作。其中之一是Blitz++,但现在似乎没有太多开发。其他替代方案是ArmadilloEigen。详情请见this previous answer

例如,使用Eigen,矩阵声明将如下所示:

MatrixXd mat(n,m);

您将能够以mat[i][j] 访问元素,并将矩阵乘以mat1*mat2,等等。

【讨论】:

  • 如果你找到一个库可以满足你的需要,那很好,但如果没有,你自己的类最合乎逻辑的底层存储是std::vector
  • @James Kanze - 我的意思是他不应该直接使用vector,因为这会涉及到太多的手动索引杂耍。围绕vector 构建一些东西很好,但是当涉及到线性代数时,如果你尝试自己实现它,你很容易比一个好的库慢 10 倍,所以我建议不要重新发明轮子这个案例。
  • 那我同意。如果抽象是一个 2D 数组,vector 并没有完全削减它;它的抽象是一维数组。但这也是管理内存块的一种非常好的方法。关于库,if Item 是某种数字类型,那么您绝对应该使用更高级的数学库。正如你所说,让它既正确又高效绝对不是微不足道的。如果不是,我不太确定。他们最终会添加很多实际上无法使用的代码(因为它假设存在像 +* 这样的运算符)。
  • 感谢您对矩阵处理的洞察力,虽然不是这个特殊问题的情况,但我确实重视这些信息。我已经用 Item 类的详细信息更新了我的原始帖子。
【解决方案3】:

第一个问题是为什么要指针。几乎没有任何理由 有一个指向std::vector 的指针,但这种情况并不常见 你会有一个指针向量。你的定义应该是:

std::vector<std::vector<Item> > items;

,或者至少(假设例如 Item 是 多态层次结构):

std::vector<std::vector<Item*> > items;

至于您的问题,最好的解决方案是以某种方式包装您的数据 属于Vector2D 类,其中包含std::vector&lt;Item&gt; 作为成员, 并进行索引计算以访问所需的元素:

class Vector2D
{
    int my_rows;
    int my_columns;
    std::vector<Item> my_data;
public:
    Vector2D( int rows, int columns )
        : my_rows( rows )
        , my_columns( columns )
    {
    }

    Item& get( int row, int column )
    {
        assert( row >= 0 && row < my_rows
                && column >= 0 && column < my_columns );
        return my_data[row * my_columns + column];
    }

    class RowProxy
    {
        Vector2D* my_owner;
        int my_row;
    public;
        RowProxy(Vector2D& owner, int row)
            : my_owner( &owner )
            , my_row( row )
        {
        }
        Item& operator[]( int column ) const
        {
            return my_owner->get( my_row, column );
        }
    };
    RowProxy operator[]( int row )
    {
        return RowProxy( this, row );
    }

    //  OR...
    Item& operator()( int row, int column )
    {
        return get( row, column );
    }
};

如果您放弃边界检查(但我不建议这样做), RowProxy 可以是简单的Item*

当然,您应该为const 复制上述内容。

【讨论】:

  • 我已经编辑了我的原始帖子以澄清我的指针用法,你的实现很有趣,我没想到这样计算索引。
猜你喜欢
  • 1970-01-01
  • 2013-07-31
  • 2013-11-28
  • 1970-01-01
  • 2013-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-11
相关资源
最近更新 更多