【问题标题】:C++: about subsequent operations of overloaded operatorC++:关于重载运算符的后续操作
【发布时间】:2016-10-20 22:39:31
【问题描述】:
#include <iostream>
#include <vector>
using namespace std;


void testfn(double* v1, double *v2, double *v3, int n);//I must use this function

class CLS{
private:
    vector<double> v;
public:
    CLS(vector<double> vin);
    CLS operator+(CLS & A);
    CLS operator*(CLS & A);
};

CLS::CLS(vector<double> vin)
{
    v=vin;
}

CLS CLS::operator*(CLS &A){
    //assuming the two vectors have the same length
    vector<double> vtmp(v.size(),0);
    testfn(&*A.v.begin(),&*v.begin(),&*vtmp.begin(),(int)A.v.size());
    CLS C(vtmp);
    return C;
}

CLS CLS::operator+(CLS &A){
    //assuming the two vectors have the same length
    vector<double> vtmp(v.size(),0);
    testfn(&*A.v.begin(),&*v.begin(),&*vtmp.begin(),(int)A.v.size());
    CLS C(vtmp);
    return C;
}

void testfn(double* v1, double *v2, double *v3, int n)
{
    for (int i=0;i<n;i++)
    {
        *v3=*v1+*v2;
        ++v1;
        ++v2;
        ++v3;
    }
}

int main(){
    vector<double> v1(100,1.0), v2(100,2.0), v3(100,0.0);
    CLS C1(v1),C2(v2),C3(v3);
    CLS C4=C1*(C1+(C2*C3+C2))*C1*C2*C3+C1+C2+C3;
    return 0;
}

我创建了 CLS 类并定义了两个运算符 + 和 *。我想像使用 + 和 * 处理整数和双精度一样简单地使用这些运算符。因此我在主CLS C4=C1*(C1+(C2*C3+C2))*C1*C2*C3+C1+C2+C3; 中有一条测试线。但是,在使用 gcc 编译此代码时出现大量错误。我对运算符重载的规则不够熟悉。我应该如何修改 * 和 + 的定义(可能只是参数?)以使CLS C4=C1*(C1+(C2*C3+C2))*C1*C2*C3+C1+C2+C3; 有效?

补充:operator+和*的const参数我不知道,因为这两个运算符的定义涉及到另一个函数testfn,其参数是double*而不是const double*。此外,我不想更改testfn 定义中的任何部分,因为在我的真实代码中,它对应于LAPACK 中的一个函数,我绝对无权更改。

【问题讨论】:

  • "我对运算符重载的规则不够熟悉。"谷歌搜索“C++ 运算符加重载”有那么难吗?
  • 在“授人以渔”行here's the reference for 'binary operation overload'。请随意阅读整个页面(适用于所有运营商)。
  • @AdrianColomitchi 我在原帖中添加了一些解释,说明为什么我不能对 + 和 * 使用 const 参数。这些困难完全有可能得到解决。但我不知道怎么做。
  • 如果您确定testfn 不会修改*v1*v2,那么您可以在调用testfn 时安全地丢弃这些参数上的const
  • @Resorter C++11 你说?也许是时候让您了解move copymove assign 而不是依赖编译器生成的了?另见the rule of 5/3/0。相关性:您按值返回对象,应该了解其背后的机制,

标签: c++ class operator-overloading


【解决方案1】:

最好将operator+operator*定义为友元函数,这样如果有从其他类型到CLS的隐式转换,则可以使用运算符。如果是成员函数,那么如果第一个操作数不是CLS,就找不到了。

friend CLS operator+(CLS lhs, const CLS& rhs) 
{
  // Do your logic here
  return lhs; 
}

【讨论】:

  • 感谢您的建议。我会记住的。它确实帮助我以更好的方式组织代码。
  • 嗨 Nikita,现在我想将矩阵乘法的定义扩展到向量 * 矩阵、矩阵 * 向量、向量 * 向量(内积)和向量 * 向量(外积)。我应该如何声明该函数以便它在所有五种情况下都有效?谢谢。
【解决方案2】:

你应该在你的代码中使用空格,这很难读。不要对类型名和变量都使用大写字母,这也很难读。

这不是必需的,只是一个小优化,通过引用传递构造函数参数,然后在构造函数初始化列表中初始化v 成员(而不是通过在正文中分配给它):

CLS::CLS(const vector<double>& vin) : v(vin)
{
}

我会添加一个新的构造函数,它采用所需的大小,并立即调整 v 成员的大小,这使得下面的代码更简单:

CLS::CLS(int n) : v(n, 0)
{
}

首先将const添加到函数参数和成员函数本身:

CLS CLS::operator*(const CLS& a) const {
                   ^^^^^         ^^^^^

然后默认为结果构造一个空的C 对象,带有所需大小的向量成员:

    //assuming the two vectors have the same length
    CLS result(v.size());

如果您创建数据向量的副本并处理这些副本,那么您可以将成员函数及其参数设为 const:

    vector<double> v1 = a.v;
    vector<double> v2 = v;

现在您可以获得指向这些向量中数据的非常量指针,并且可以将输出直接写入C.v,而不是写入vtmp,然后将其复制到返回的对象中:

    testfn(&*v1.begin(), &*v2.begin(), &*result.v.begin(), v.size());

最后返回结果:

    return result;
}

其他功能的变化完全相同:

CLS CLS::operator+(const CLS& a) const {
    //assuming the two vectors have the same length
    CLS result(v.size());
    vector<double> v1 = a.v;
    vector<double> v2 = v;
    testfn(&*v1.begin(), &*v2.begin(), &*result.v.begin(), v.size());
    return result;
}

现在这将能够接受 const 参数,因此应该使用由每个 C1+C2C3*C4 表达式创建的临时对象。

由于数据的额外副本,代码效率略低,但构造函数中的优化有助于实现这一点。如果您能够使用 C++11,即使是这些低效率的问题也可以得到解决,但需要更多的工作。

首先,C++11提供vector&lt;T&gt;::data()获取指针,比&amp;*v.begin()清晰很多。 (它不是 C++11 之前的 C++ 标准的一部分,但 GCC 甚至为 C++98 提供了它)。您可以将上面显示的代码更改为使用v.data() 而不是&amp;*v.begin(),如下所示。

在 C++11 中,您可以通过将(非常量)右值引用绑定到临时对象来添加适用于临时对象的额外重载。因为右值引用为您提供对临时对象的非 const 访问,您无需复制其数据即可获得非 const 指针,并且可以直接使用它们。除了上面显示的原始运算符之外,添加这些运算符重载:

CLS CLS::operator+(CLS&& a) && {
    //assuming the two vectors have the same length
    CLS result(v.size());
    testfn(a.v.data(), v.data(), result.v.data(), v.size());
    return result;
}

CLS CLS::operator*(CLS&& a) && {
    //assuming the two vectors have the same length
    CLS result(v.size());
    testfn(a.v.data(), v.data(), result.v.data(), v.size());
    return result;
}

这可以避免在算术表达式创建的中间临时对象中创建任何副本。我还发现它非常更易于理解和阅读。

完整的工作示例位于http://coliru.stacked-crooked.com/a/6e40d20eb7f1e9fc

【讨论】:

  • 您的优化建议绝对有意义。制作 CLS 的副本是众所周知的,但正如您所说,它的效率较低。我确实在使用 C++11,你能告诉我如何完全解决这个问题吗?谢谢。
  • 我在您定义结果时更正了您的错字。但是为什么它仍然无法在我自己的计算机上编译?我将 + 声明为 CLS 运算符+(CLS&& a) &&;公开之下。然后我只是复制了你的代码并纠正了错字,但它仍然没有编译。
  • 我修正了一些错别字,并添加了一个完整工作示例的链接。确保将新的重载运算符添加到类定义中,并将const 添加到类定义中的原始声明中。
  • 太棒了。非常感谢!
  • 抱歉,您能避免在 + 和 * 中复制整个 CLS 吗?
【解决方案3】:

testfn 与所有参数非 const 一起使用的不寻常要求有点奇怪,但它......嗯...... 勉强接受对于复杂表达式的情况是不可接受的 - 第一个testfn 的两个参数需要是const double*

理由: C++ 标准要求将临时对象绑定到 const references - 一些 interesting details here

@Resorter 说:

“感谢您告诉我,我很想学习。但是您确定解决了我的问题吗?我真的很想先解决这个问题。”

[其中 it 是“移动构造函数”和“移动赋值”]


以下“鱼”假设您已阅读/学习钓鱼move constructormove assignrules of 5/3/0

补充学习材料:copy elision - 当允许编译器跳过复制/移动构造函数时,即使它们会产生副作用。

// some implementations
void testfn0(const double* v1, const double *v2, double *v3, int n) {
  for(int i=0; i<n; i++) {
    v3[i]=v1[i]+v2[i];
  }
}

void testfn1(double* v1, double *v2, double *v3, int n) {
  for(int i=0; i<n; i++) {
    v3[i]=v1[i]*v2[i];
  }
}

class CLS {
  std::vector<double> v;
public:
  // constructor taking a const reference
  CLS(const std::vector<double>& in) : v(in) {
    std::cout << "copy vec" << std::endl;
  }

  // extra constructor with rvalue reference: it will "canibalize" the parameter
  CLS(std::vector<double>&& in) : v(in) {
    std::cout << "move vec" << std::endl;
  }

  // Rule of 5 applied
  // Copy constructor
  CLS(const CLS& other) : v(other.v) {
    std::cout << "copy CLS" << std::endl;
  }
  // Move constructor
  CLS(CLS&& other) : v(std::move(other.v)) {
    std::cout << "move CLS" << std::endl;
  }
  ~CLS() { }

  // Copy assgn
  CLS& operator=(const CLS& o) {
    this->v=o.v;
    std::cout << "assgn CLS" << std::endl;
    return *this;
  }
  // Move assgn
  CLS& operator=(CLS&& other) {
    this->v=std::move(other.v);
    std::cout << "move CLS" << std::endl;
    return *this;
  }

  // WILL NOT WORK WITHOUT const FOR COMPLEX EXPRESSIONS
  //                       |
  //              ----------
  //              V
  CLS operator+(const CLS& rhs) {
    std::vector<double> vtmp(v.size(),0);
    testfn0(&*rhs.v.begin(),&*v.begin(),&*vtmp.begin(),(int)rhs.v.size());

    CLS ret(std::move(vtmp));
    return ret;
  }

  // WILL NOT WORK WITHOUT const FOR COMPLEX EXPRESSIONS
  //                       |
  //              ----------
  //              V
  CLS operator*(const CLS& rhs) {
    std::vector<double> vtmp(v.size(),0);
    testfn1(&*rhs.v.begin(),&*v.begin(),&*vtmp.begin(),(int)rhs.v.size());

    CLS ret(std::move(vtmp));
    return ret;
  }
};

int main() {
  std::cout << "\n--- inits " << std::endl;
  CLS a(std::vector<double>{1,2,3}); // move vec
  CLS b(std::vector<double>{3,2,1}); // move vec

  std::cout << "\n--- add " << std::endl;
  CLS c=a+b; // move vec and 'silence' (copy elision)
  std::cout << "\n--- mul " << std::endl;
  CLS d=a*b; // move vec and 'silence' (copy elision)

  std::cout << "\n--- copy " << std::endl;
  CLS m=c; // Copy CLS - constructor

  std::cout << "\n--- move " << std::endl;
  CLS n=std::move(d); // Move CLS - constructor

  std::cout << "\n--- assgn (d=c) and copy (x=c)" << std::endl;
  CLS x=d=c;
}

【讨论】:

  • @Resorter 啊,(以防万一)别忘了用-std=c++11或更好的方式编译
  • 老大,你的代码没有问题。我可以编译并运行它。但我希望 + 和 * 以 x=a+b*c+d 的方式使用。我添加了这一行,然后我再次编译失败。
  • @Resorter 你是对的。您确定 testfn 必须具有 all-no-const 签名而不是 void testfn(const double* v1, const double *v2, double *v3, int n) 吗?为什么前 2 个参数不能是 const?该功能(那些功能)到底应该做什么?
  • 我必须在我的真实代码中使用这个(和其他一些)LAPACK 函数
  • extern "C" { void DGEMM ( char *TRANSA, char *TRANSB, int *M, int *N, int *K, double *ALPHA, double *A, int *LDA, double * B, int *LDB, double *BETA, double *C, int *LDC);
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-16
  • 1970-01-01
  • 1970-01-01
  • 2015-01-07
  • 2018-05-30
相关资源
最近更新 更多