C++ 添加 2 个 3D 矢量返回垃圾值

C++ adding 2 3D vectors returns garbage value

提问人:Sync it 提问时间:10/22/2020 更新时间:10/23/2020 访问量:180

问:

我对 c++ 非常陌生,刚刚开始学习运算符重载。这是我的尝试

class Vector
{
 public:
 float x=0,y=0,z=0;
 Vector(float x, float y, float z) :x(x),y(y),z(z) {}
 Vector(Vector& copy) :x(copy.x),y(copy.y),z(copy.z){ std::cout << "Copy Created" << std::endl;} //Testing if any new objects were created during the operator overloading process[For my purpose this should not be called as no new objects should be created except than the returned result of each operator]

 public:
 Vector& operator+(Vector& v1) //return this+v1
 {
     Vector v(this->x+v1.x,this->y+v1.y,this->z+v1.z);
     return v;
 }
 Vector& operator-(Vector& v1) //return this-v1
 {
     Vector v(this->x - v1.x, this->y - v1.y, this->z - v1.z);
     return v;
 }
 Vector& operator*(Vector& v1) //return this cross v1
 {
     Vector v(this->y * v1.z-this->z * v1.y, -this->x * v1.z + this->z * v1.x, this->x * v1.y - this->y * v1.x);
     return v;
 }
}

std::ostream& operator<<(std::ostream& output, Vector& v)  
{
    output << v.x << "," << v.y << "," << v.z << std::endl;
    return output;
}

int main()
{
    Vector
    v1(1, 2, 3),
    v2(4, 5, 6);

    Vector
    v3 = v1 + v2,
    v4 = v1 - v2,
    v5 = v1 * v2;
  
    std::cout << v3 << v4 << v5;

  return 1;
}

打印时,所有 3 个向量都有垃圾值,并且每个操作调用了 copy 构造函数 3 次。我已经通过引用传递了每个向量,但仍然在我不理解的地方创建了一个新的临时实例。

我还尝试按照以前的线程的建议将关键字 const 添加到运算符及其参数中,但它并没有解决问题

由于我是新手,因此对解决方案的详细解释将不胜感激。谢谢

C++ 运算符重载 copy-constructor

评论

7赞 Botje 10/22/2020
如果在编译器上显示警告,它应该会告诉您正在返回对临时(局部)变量的引用。不要这样做,而是返回副本。
0赞 463035818_is_not_an_ai 10/22/2020
运算符 ,并且应该返回一个值而不是引用。看这里: stackoverflow.com/questions/4421706/...+*-
2赞 463035818_is_not_an_ai 10/22/2020
(请注意,这是“应该”,这是传统方式,原则上这些运算符可以返回任何内容,但返回对本地的引用在任何情况下都是错误的)
0赞 Sync it 10/22/2020
@Botje你是对的,我确实打开了编译器的警告,它确实向我显示了警告,但是当输出出现时它立即消失了,所以我看不到它:)
0赞 463035818_is_not_an_ai 10/22/2020
它有助于将警告视为错误。 与 GCC 合作-Werror

答:

0赞 Nikita Mirošničenko 10/22/2020 #1

不确定,但我认为您得到垃圾值是因为您试图返回对本地堆栈值 v 的引用。在重载运算符的作用域结束后,“v”值可能会被程序的另一部分覆盖。

可能的解决方案是:

Vector& operator+(Vector& v1) //return this+v1
{
    return *new Vector(this->x+v1.x,this->y+v1.y,this->z+v1.z);
}

评论

1赞 Sync it 10/22/2020
谢谢你对我有用。看到你的答案后,我刚才查了一个关于动态与堆栈内存的教程,我明白了很多。每个小型教程网站都从未建议过这样做,他们会错误地显示正确输出的屏幕截图,这让我难倒了一段时间。我猜细节中的魔鬼:)
0赞 463035818_is_not_an_ai 10/22/2020
这看起来像是大量内存泄漏的秘诀。谁会去那个?当您调用分配的堆栈时,思考会变得非常复杂,因为您需要跟踪哪些是新的,哪些不是手动的。deleteVectoroperator+VectorVector
0赞 463035818_is_not_an_ai 10/22/2020
这不是一个好的解决方案。它使 OP 代码编译时没有错误,但它会导致问题
0赞 Sync it 10/22/2020
好吧,答案确实对我有用,但是现在在阅读了您的评论之后,我应该将其取消标记为已接受的答案吗?有更好的选择吗?
0赞 Netherwire 10/22/2020
味道有点,但是......尽管如此,仍然有效:)
3赞 463035818_is_not_an_ai 10/22/2020 #2

运算符 ,并通常返回副本而不是引用:+*-

Vector operator+(const Vector& v1) const
  //^^ no &
                //^^ added const  
                                  //^^ added const
{
   Vector v(this->x+v1.x,this->y+v1.y,this->z+v1.z);
   return v;
}

此外,您还应该将该方法声明为 ,以便您可以调用常量向量。参数应作为参考。constoperator+const

有关运算符重载的更多详细信息,请参阅此处:运算符重载的基本规则和习语是什么?

在代码中,您返回的是对局部变量的引用,该变量总是错误的。一个简化的例子是

int& foo() {
   int x = 0;
   return x;
}

局部变量的生存期在函数返回并且调用方获得悬空引用时结束。使用该引用将调用未定义的行为x

如果确实要避免副本,则应重载复合运算符 , , 。它们应就地执行操作,并且通常在修改操作后返回对操作的引用。有关详细信息,请参阅上面的链接。+=*=-=this

我在上面写了“应该”、“通常”和“常规”。操作员过载相当灵活,可以做最奇怪的事情。但是,约定是从运算符 和 返回一个新值,并且返回对局部变量的引用总是错误的。+*-

最后但并非最不重要的一点是,我想提一下,看到一个初学者代码应用了如此少的不良做法,这令人耳目一新。除了你的错误,我唯一要批评的是一致性的正确性。默认生成方法和参数。仅当您需要修改它们时,才使它们非常量。例如,你也应该采取一个,因为它不会修改它。constoperator<<const Vector&

评论

0赞 Sync it 10/22/2020
感谢您的初学者提示。它转向在任何地方添加 const,包括在“COPY CONSTRUCTOR”本身 Vector(const Vector& v) 最终解决了这个问题。我将对 const 修饰符进行更多研究,以学习更好的实践
0赞 463035818_is_not_an_ai 10/22/2020
@RVISHAL不,仅靠添加并不能解决问题!请注意,未定义的行为意味着您的代码是错误的,但不一定在编译过程中收到任何警告或错误。当您运行具有 UB 的代码时,它可能看起来可以工作,但它仍然是错误的。您需要修复返回对局部变量的引用!const
0赞 463035818_is_not_an_ai 10/22/2020
@RVISHAL缺失会在其他代码中产生问题,而不是在您的代码中(除非我遗漏了某些内容)const
0赞 Sync it 10/22/2020
我更改了我的代码。我删除了对仅返回值的返回引用,但我不想编辑我的问题,以便将来有人看到这篇文章时会理解我的错误,并通过阅读您的答案来纠正它
0赞 463035818_is_not_an_ai 10/22/2020
@RVISHAL这是正确的方法。你永远不应该在问题中修复代码中的问题(除非它与实际问题完全无关)。问题是针对损坏的代码,固定的代码是答案。
2赞 ytlu 10/23/2020 #3

根据您的代码,我建议将运算符更改为 operator+= -= 和 *=。它们非常符合您的目的。在每个操作结束时,返回 *this。这意味着返回当前工作对象。例如:

vector& vector::operator+= (const vector& v) 
 { this->x += v.x;
   this->y += v.y;
   this->z += v.z;
   return *this; 
 }

并将 operator+ 写成两个参数的 friend 函数:

 vector operator+(const vector&v1, const vector&v2)
  { vector v3(v1);  // copy constructor
    v3 += v2;
    return v3;
  }