【问题标题】:Modifying a std::vector function (inheritance?)修改 std::vector 函数(继承?)
【发布时间】:2015-10-27 22:06:32
【问题描述】:

我正在将一些 Fortran90 代码移植到 C++(因为我很愚蠢,为了保存“为什么?!”)。

Fortran 允许指定数组的范围,特别是从负值开始,例如

double precision :: NameOfArray(FirstSize, -3:3)

我可以用 C++ 写成类似的东西

std::array<std::array<double, 7>, FirstSize> NameOfArray;

但现在我必须像 NameOfArray[0:FirstSize-1][0:6] 这样的索引。如果我想使用 Fortran 样式索引进行索引,我可以写也许

template <typename T, size_t N, int start>
class customArray
{
public:
    T& operator[](const int idx) { return data_[idx+start]; }
private:
    std::array<T,N> data_;
}

然后

customArray<double, 7, -3> NameOfArray;
NameOfArray[-3] = 5.2;
NameOfArray[3] = 2.5;
NameOfArray[4] = 3.14; // This is out of bounds, 
                       // despite being a std::array of 7 elements

所以 - 总体思路是“不要从 std::'container class here' 继承”。 我的理解是,这是因为例如 std::vector 没有虚拟析构函数,因此不应该(不能?)多态使用。

还有其他方法可以让我使用std::arraystd::vector 等,并“免费”获得它们的功能,同时覆盖特定功能吗?

template<typename T, size_t N>
T& std::array<T,N>::operator[](const int idx) { ... };

可能允许我覆盖运算符,但它不会让我访问有关自定义起点的知识 - 使其完全没有意义。此外,如果我乐观地认为我的所有 customArray 对象都具有相同的偏移量,我可以对该值进行硬编码 - 但随后我的 std::array 已损坏(我认为)。

我该如何解决这个问题? (忽略简单的答案-不要-根据需要写myArray[idx-3]

【问题讨论】:

  • 只要你不添加任何应该被析构函数清理的东西(例如非 POD 变量),从 std 向量继承应该是可以的(虽然,可能会有更好的解决您的问题)
  • 如果你是从 Fortran 翻译的,你几乎不会多态地使用向量,是吗?然后非虚析构函数参数落下。
  • 对不起 - 再次阅读我意识到它读起来好像我在说作为不在这里这样做的理由 - 我的意思是我理解它通常是应该避免的。我明白你的两个观点——也许这是最简单的解决方案。关于“更好的解决方案” - 有什么建议吗? (虽然现在我想这可能是 SO 的题外话)
  • 我已经做过很多次了,之后一直很开心。

标签: c++ c++11 inheritance operator-overloading overloading


【解决方案1】:

继承标准容器没有问题。这只是通常不鼓励,因为这会带来一些限制,而且这种继承并不是最初在 C++ 中预测的继承方式。如果您小心并意识到这些限制,则可以在此处安全地使用继承。

你只需要记住这不是子类化,这意味着什么。特别是,您不应该使用指向此类对象的指针或引用。问题可能是如果您传递了 MyVector&lt;x&gt;* 的值,而 vector&lt;x&gt;* 是预期的。你也不应该创建这样的动态对象(使用new),因此delete这些对象也应该通过指向基类的指针来创建——因为析构函数调用不会转发到你的类的析构函数,因为它不是虚拟的

不可能阻止将“派生指针”转换为“基指针”,但您可以通过重载&amp; 运算符来阻止从对象中获取指针。您还可以通过在私有部分中声明类内operator new 来防止动态创建此类的对象(或者= delete 也应该起作用)。

不要同时考虑私有继承。这就像在私有部分中包含这个东西一样作为一个字段,除了访问者名称。

【讨论】:

  • 一个有用的答案,用简单的术语详细说明了一些问题,并附有不该做什么的具体示例。也感谢关于私有继承的评论——这有时会出现在类似的问题中,然后下面的 using std::vector::push_back(...) 等声明似乎超越了一切。
  • 为什么不应该是子类化?特别是传递原始指针完全没有问题。唯一可能出现问题的情况是,如果您通过基指针进行删除,但是当您使用智能指针时,即使该参数也会下降。
  • 子类化意味着您将基类用作框架,在其中您的实现覆盖了一些插槽。派生通常是两件事:扩展和子类化。如果您派生非多态类,显然只有扩展。我曾经写过一篇文章:sektorvanskijlen.wordpress.com/2010/12/08/…
  • 我不同意第三和第四段。私有基类class customArray : private std::vector&lt;float&gt; do 防止在类外部转换为基类指针,但在类内部(您知道要小心)您可以编写using 声明(您不能为会员做)
  • @MSalters,我在回答中正是使用了这种方法(private 继承和using)。 (不确定您是否先评论过!)private 继承非常适合这个问题,因为在这种情况下我们真的不想要 is-a 关系。我们想通过“复制和粘贴”array 中的代码并修改一些内容来构建一个新的、独特的类型customArray。我想我们同意这一点?
【解决方案2】:

范围转换器类可能是解决方案,尽管您需要自己制作,但它可以让您获得范围大小以初始化向量并进行转换。

未经测试的代码:

struct RangeConv // [start,end[
{
  int start, end;
  RangeConv(int s, int e) : start(s), end(e) { }
  int size() const { return end - start; }
  int operator()(int i) { return i - start; } // possibly check whether in range
}

RangeConv r(-3, 3);
std::vector<int> v(r.size());
v[r(-3)] = 5;

【讨论】:

  • 一个有趣的想法,但这实际上与v[idx - offset] 相同。尽管如此,外观稍微好一点,并且避免了内存分配、指针类型等方面的任何问题。
【解决方案3】:

所以不应该(不能?)多态地使用。

不要太早放弃。 C++ 中的继承基本上有两个问题需要考虑。

终身

如果您基本上遵循一个简单的规则,那么此类对象(在基类中具有非虚拟析构函数的派生类)可以安全地以多态方式使用:不要在任何地方使用delete。这自然意味着您不能使用new。无论如何,您通常应该避免使用现代 C++ 中的 new 和原始指针。 shared_ptr 会做正确的事,即安全地调用正确的析构函数,只要你使用make_shared

std:: shared_ptr<Base> bp = std:: make_shared<Derived>( /* constructor args */ );

make_shared 的类型参数,在本例中为 Derived,不仅控制创建的类型。它还控制调用哪个析构函数。 (因为底层的共享指针对象会存储一个合适的删除器。)

使用unique_ptr 很诱人,但不幸的是(默认情况下)它会导致使用错误的删除器(即它会天真地直接在基指针上使用delete)。不幸的是,除了默认的unique_ptr,标准中还没有内置一个更安全但效率更低的unique_ptr_with_nice_deleter

多态性

即使std::array 确实有一个虚拟析构函数,这个当前的设计仍然会很奇怪。因为operator[] 不是虚拟的,所以从customArray* 转换为std:: array* 会导致错误的operator[]。这不是一个真正的 C++ 特定问题,基本上是你不应该假装 customArray isa std:: array 的问题。

相反,只需确定customArray 是一个单独的类型。这意味着您不能将 customArray* 传递给期望 std::array* 的函数 - 但您确定还是要这样做吗?

有没有其他方法可以让我使用 std::array、std::vector 等,并“免费”获取它们的函数,同时重载特定函数?

这是个好问题。你确实希望你的新类型满足isastd::array。您只希望它的行为与它非常相似。就好像您神奇地复制并粘贴了 std::array 中的所有代码以创建一个新类型。然后你想调整一些东西。

使用private继承和using子句引入你想要的代码:

template <typename T, size_t N, int start>
struct customArray : private std::array<T,N>
{
    // first, some functions to 'copy-and-paste' as-is
    using std::array<T,N>  :: front;
    using std::array<T,N>  :: begin;

    // finally, the functions you wish to modify
    T& operator[](const int idx) { return data_[idx+start]; }
}

private 继承将阻止从 customArray *std::array * 的转换,这就是我们想要的。

PS:我对像这样的private 继承很少有经验。这么多它不是最好的解决方案 - 任何反馈表示赞赏。

【讨论】:

    【解决方案4】:

    总体思路

    建议不要从标准向量继承,因为这种构造经常被误解,有些人试图让所有类型的对象都从向量继承,只是为了一点点方便。

    但这条规则不应该成为教条。特别是如果您的目标是创建一个矢量类,并且您知道自己在做什么。

    危险一:不一致

    如果您有一个非常重要的代码库使用 1..size 而不是 0..size-1 范围内的向量,您可以选择根据此逻辑保留它,以免将数千个 -1 添加到索引,+1 显示索引,+1 大小。

    一个有效的方法可能是使用类似的东西:

    template <class T> 
    class vectorone : public vector<T> {
        public: 
        T& operator[] (typename vector<T>::size_type n) { return vector<T>::operator[] (n-1); }
    const T& operator[] (typename vector<T>::size_type n) const { return  vector<T>::operator[] (n-1); }
    };
    

    但你必须在所有矢量界面上保持一致:

    • 首先,还有一个const T&amp; operator[]()。如果你不重载它,如果你在常量对象中有向量,你最终会出现错误的行为。
    • 然后,上面没有了,还有一个at(),它应该与[]一致
    • 那么您必须格外小心构造函数,因为它们有很多,以确保您的参数不会被误解。

    所以你有免费的功能,但还有比最初想象的更多的工作。使用更有限的接口和私有向量创建自己的对象的选项最终可能是一种更安全的方法。

    危险2:更多的不一致

    矢量索引为vector&lt;T&gt;::size_type。不幸的是,这种类型是无符号的。必须仔细分析从向量继承但使用有符号整数索引重新定义operator[] 的影响。根据定义索引的方式,这可能会导致细微的错误。

    结论:

    您认为提供一致的std::vector 界面可能还有更多工作要做。所以最后,使用私有向量拥有自己的类可能是更安全的方法。

    您还应该考虑到有一天您的代码将由没有 fortran 背景的人维护,他们可能对您的代码中的 [] 有错误的假设。使用原生 C++ 真的没有问题吗?

    【讨论】:

      【解决方案5】:

      坚持组合并为您需要的成员函数编写包装器似乎并没有那么糟糕。没有那么多。我什至想将array 数据成员公开,以便您可以在需要时直接访问它,尽管有些人会认为这比从没有虚拟析构函数的基类继承更大。

      template <typename T, size_t N, int start>
      class customArray
      {
      public:
          std::array<T,N> data;
      
          T& operator[](int idx) { return data[idx+start]; }
          auto begin() { return data.begin(); }
          auto begin() const { return data.begin(); }
          auto end() { return data.end(); }
          auto end() const { return data.end(); }
          auto size() const { return data.size(); }
      };
      
      int main() {
          customArray<int, 7, -3> a;
          a.data.fill(5);  // can go through the `data` member...
          for (int& i : a) // ...or the wrapper functions (begin/end).
              cout << i << endl;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-10-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多