【问题标题】:Overload operator + for vector: namespace std重载运算符 + 用于向量:命名空间 std
【发布时间】:2019-02-05 19:22:33
【问题描述】:

我正在尝试为 std::vector 重载运算符 + 和 +=,而我所做的是

namespace std {
    template<class T>
    vector<T> operator+(vector<T> x, vector<T> y) {
        vector<T> result;
        result.reserve(x.size());
        for (size_t i = 0; i < x.size(); i++)
            result[i] = x[i] + y[i];
        return result;
   }
}

但我认为这是不好的做法,因为 clang-tidy 警告我“修改 std 命名空间可能导致未定义的行为”。为 STL 类重载运算符还有其他更好的做法吗?

【问题讨论】:

  • 只是不要把重载放在std命名空间中?
  • 首先,你想要vector&lt;T&gt;&amp;,而不是vector&lt;T&gt;
  • 除了极少数例外,不允许将定义放置在 std 命名空间中,不清楚为什么您认为在这种情况下需要这样做。
  • 你可以在这部分找到答案:stackoverflow.com/questions/3123699/…
  • 在这里找到答案:stackoverflow.com/questions/3123699/…

标签: c++ vector std


【解决方案1】:

最好的做法是不要这样做。

但如果你真的想要你仍然可以:只是不要把它放在命名空间std

并且不要按价值来衡量你的论点,除非你故意这样做是为了充分利用移动语义(你不是)。

【讨论】:

  • 按价值取其中之一是合理的。
  • @Evgeny 不合理,因为您可能想要退回它,但它不会成为 NRVO 的候选者。
  • 重载不在该类型命名空间中的操作符通常是个坏主意。它真的不太好用。而且,您绝对应该使用像std::vector 这样的廉价移动类型来按值取第一个参数;它使a+b+c+d 非常高效。不是第二个。
  • @Yakk-AdamNevraumont,对a+b+c+d 的评价非常好。更正了我的答案。
  • @Yakk-AdamNevraumont 是的,公平地说。
【解决方案2】:

我建议:

  1. 不要使运算符过载。改为创建常规函数。
  2. 将函数放入特定于您的应用的namespace

例子:

namespace MyApp
{
   template <typename T>
   std::vector add(std::vector<T> const& lhs, std::vector<T> const& rhs) { ... }

   template <typename T>
   std::vector& append(std::vector<T>& lhs, std::vector<T> const& rhs) { ... }
}

【讨论】:

    【解决方案3】:

    将函数插入std 会使您的程序格式错误,无需诊断。

    在某些有限的情况下,您可以在std 中插入专业化,但这不能满足您的需要。

    所以你不能将vec + vec插入std

    将运算符放在不同的命名空间中是合法的,但不明智。当无法通过 ADL/Koenig 查找找到运算符时,它们无法正常工作。看似合理的代码,例如 std::accumulate( vec_of_vec.begin(), vec_of_vec.end(), std::vector&lt;int&gt;{} ) 编译失败等问题。

    简短的回答是,vector 不是你喜欢的类型。不要这样做。

    您可以在其他地方创建辅助函数,例如 util::elementwise_add( vec, vec )

    std 没有实现+,因为连接和元素操作都是合理的。 valarray 确实实现了元素操作;可能你想要的是std::valarray&lt;int&gt; 而不是std::vector&lt;int&gt;

    如果所有这些都失败了,您可以编写一个命名运算符 vec +by_elem+ vec 或从您自己的命名空间中的 std::vector 继承,使用该类型,并为您的类型重载 +。 (从std::vector 继承是非常安全的;只要没有人使用堆分配的指向std::vectors 或类似的原始指针)

    【讨论】:

    • 非常彻底。虽然命名运算符是邪恶的 ;)
    • C++14 [namespace.std] 说它是 UB,而不是格式错误的 NDR,尽管这可能已经改变了
    【解决方案4】:

    无论你是作为operator+(...)还是作为一个函数add(...)来实现加法,你最好这样做:

    template<class T>
    std::vector<T> operator+(std::vector<T> x, const std::vector<T>& y) {
        assert(x.size() == y.size());
        for (std::size_t i = 0; i < x.size(); ++i)
            x[i] += y[i];
        return x;
    }
    

    通过按值(而不是按 const-ref)获取第一个向量,您将强制编译器自动为您制作副本以保存结果。

    补充阅读this comment后。

    由于+ 的从左到右的关联性,像a + b + c 这样的表达式被解析为(a + b) + c。因此,如果operator+(... x, ... y) 中的第一个(而不是第二个)参数按值取值,则a + b 返回的prvalue 可以移动到x。否则,将制作不必要的副本。

    【讨论】:

    • 注意x不受NRVO约束,不好。另一方面,它是可以移动的,而且vector的移动成本很低,所以也不算太糟糕。
    • @juanchopanza 我不同意“不好”。在这种情况下,真的没关系。
    • @Yakk-AdamNevraumont 值得了解这一点,因为并非所有类型都可以廉价移动。
    【解决方案5】:

    您可能应该创建自己的向量类,继承自 std::vector 并定义新的运算符

    #include <iostream>
    #include <vector>
    
    template<class T>
    class MyVec : public std::vector<T>
    {
    public:
        MyVec& operator+=(const T& add)
        {
            reserve(1);
            push_back(add);
            return *this;
        }
    };
    
    int main()
    {
        MyVec<int> vec;
        vec += 5;
        vec += 10;
    
        for (auto& e : vec)
        {
            std::cout << e << "\t";
        }
    
        std::cin.get();
    }
    

    编辑:对不起,我不知道这个解决方案会导致未定义的行为。然后我会建议上面帖子中显示的类似解决方案。但是为什么需要加号运算符? push_back 还不够好吗?您可以实现参考返回值以继续添加。所以你可以这样做:

    vec + 20 + 30;
    

    添加两个元素(20 和 30)。这是更少的代码,但更具可读性?

    【讨论】:

    • 不建议。该标准不允许您创建std::vector 的子类。
    • 好的,我不知道。所以它编译但它的未定义行为?
    • 好的,谢谢,我学会了。只是想帮助,生病留给专业人士:)
    • @RSahu 标准怎么不允许呢?听起来很奇怪。
    • @RSahu 上面的答案中没有任何未定义的行为,这只是不好的做法。如果有人做了 std::vector&lt;int&gt;* p = new MyVec&lt;int&gt;(); delete p; 的行为未定义,因为 vector 没有 virtual 析构函数。
    猜你喜欢
    • 2011-12-03
    • 1970-01-01
    • 2019-06-18
    • 1970-01-01
    • 2010-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多