【问题标题】:Overloading operator [] for a sparse vector为稀疏向量重载运算符 []
【发布时间】:2010-11-26 01:03:10
【问题描述】:

我正在尝试在 C++ 中创建一个“稀疏”矢量类,如下所示:

template<typename V, V Default>
class SparseVector {
    ...
}

在内部,它将由std::map&lt;int, V&gt; 表示(其中V 是存储值的类型)。如果地图中不存在某个元素,我们将假设它等于模板参数中的值Default

但是,我在重载下标运算符[] 时遇到了问题。我必须重载 [] 运算符,因为我将此类中的对象传递到期望 [] 正常工作的 Boost 函数中。

const 版本很简单:检查索引是否在地图中,如果是则返回其值,否则Default

但是,非常量版本要求我返回一个引用,这就是我遇到麻烦的地方。如果该值只是读取,我不需要(也不想)向地图添加任何内容;但如果它正在编写,我可能需要在地图中添加一个新条目。问题是重载的[] 不知道一个值是读取还是写入。它只是返回一个引用。

有什么办法可以解决这个问题吗?或者也许可以解决它?

【问题讨论】:

  • boost::mapped_vector 应该做类似的事情 - 你可以研究它以获得想法(或者只是使用它)。
  • 它不支持我的默认值,而且,我打算对二维矩阵执行此操作,因此直接使用它是不可能的。但仍然是一个有用的参考!

标签: c++ operator-overloading subscript sparse-matrix


【解决方案1】:

可能有一些非常简单的技巧,但除此之外,我认为operator[] 只需返回可以从 V 分配(并转换为 V)的东西,不一定是 V&。所以我认为你需要返回一些带有重载operator=(const V&amp;) 的对象,这会在你的稀疏容器中创建条目。

不过,您必须检查 Boost 函数对其模板参数的作用 - 用户定义的到 V 的转换会影响可能的转换链,例如通过防止在同一链中出现更多用户定义的转换.

【讨论】:

  • 这是唯一的解决方案。另一方面,代理对象也不是完美的——例如,如果V 有任何重载的转换运算符,代理将无法无缝传播它们。
  • 啊刚刚测试了提升向量。看起来他们也有这种行为。很有趣。
  • (而且vector&lt;bool&gt; 也不是 STL 容器。)
  • 对于 boost 算法的使用可能没问题,但是如果希望用一个临时变量(比如 V&)捕获结果,会发生什么?
  • 嗯,没有测试我会说编译失败(如果引用是非常量),或者临时绑定到变量(如果引用是常量)。 operator[] 在序列接口 (23.1.1) 中是可选的。此容器未按照该表中的定义实现它(返回 T 的左值并分别采用表 68 和第 12 条中指定的摊销常数时间):采用迭代器的算法仅要求 operator* 可从 T 分配,而不是它返回T&amp;,我期待/希望这个使用 [] 的未指定 Boost 函数也是如此。
【解决方案2】:

我想知道这个设计是否合理。

如果你想返回一个引用,这意味着类的客户端可以将调用operator[]的结果存储在一个引用中,并在以后的任何时候读取/写入它。如果您不返回引用,和/或每次处理特定索引时不插入元素,他们怎么能这样做? (另外,我觉得该标准需要一个适当的 STL 容器,提供 operator[] 以让该运算符返回一个引用,但我不确定。)

您也许可以通过给您的代理还一个operator V&amp;()(这将创建条目并分配默认值)来规避这一点,但我不确定这在某些情况下是否会打开另一个漏洞我还没有想到。

std::map 通过指定该运算符的非常量版本始终插入一个元素(并且根本不提供const 版本)来解决此问题。

当然,你总是可以说这个不是现成的 STL 容器,operator[] 不返回用户可以存储的普通引用。也许没关系。我只是好奇。

【讨论】:

  • 关于设计的合理性:它是通用向量的内存优化,用于特定情况 - 所以接口/合同/文档应该涵盖这一点。关于引用的有效性:即使是 stl 迭代器也不总是有效的。
  • @xtofl:但是,STL 容器非常仔细地定义了哪些操作可以使迭代器和/或对容器元素的引用无效。这个容器也应该这样做,并且用户必须确保使用该类作为参数的任何模板都不会提出它无法满足的要求。
【解决方案3】:

不要让非常量 operator& 实现返回一个引用,而是一个代理对象。然后,您可以实现代理对象的赋值运算符,以区分对 operator[] 的读取访问和写入访问。

这里有一些代码草图来说明这个想法。这种方法并不漂亮,但很好——这是 C++。 C++ 程序员不会浪费时间参加选美比赛(他们也没有机会)。 ;-)

template <typename V, V Default>
ProxyObject SparseVector::operator[]( int i ) {
   // At this point, we don't know whether operator[] was called, so we return
   // a proxy object and defer the decision until later
   return ProxyObject<V, Default>( this, i );
}

template <typename V, V Default>
class ProxyObject {
    ProxyObject( SparseVector<V, Default> *v, int idx );
    ProxyObject<V, Default> &operator=( const V &v ) {
      // If we get here, we know that operator[] was called to perform a write access,
      // so we can insert an item in the vector if needed
    }

    operator V() {
      // If we get here, we know that operator[] was called to perform a read access,
      // so we can simply return the existing object
    }
};

【讨论】:

  • 有什么方法可以让类型转换为例如const int &amp;int &amp;,并让编译器在可以使用不可写左值时选择前者?我想让代理对象返回对从主对象加载的字段的引用,然后让其析构函数将该字段复制回主对象(如果它可能已更改),但我不知道如何避免对只读访问的回写。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-26
  • 1970-01-01
  • 2013-11-10
  • 2013-05-15
相关资源
最近更新 更多