【问题标题】:Why is this object is not getting copied by value为什么这个对象没有被值复制
【发布时间】:2015-03-14 10:08:54
【问题描述】:

当我在 operator* 函数中返回对象 temp 时,它不会被复制到 main 中的 p3。

operator* 函数中的 cout 语句返回正确的值,但 main 中的 p3 只有垃圾。

另外,我在 main 的 return 0 语句中得到一个 _block_type_is_valid(phead->nblockuse)。

这里是代码。

#pragma once
#include<iostream>
using namespace std;
class Polynomial
{
private:
    int *coefficients;
    int length;
public:
    inline int getLength() const {return length;}
    Polynomial(int length);
    Polynomial(const Polynomial &p);
    Polynomial():coefficients(nullptr){}
    ~Polynomial(void);
    extern friend Polynomial operator*(const Polynomial &p1,const Polynomial &p2);
    inline int operator[](int n) const { return coefficients[n];}
    inline int& operator[](int n) { return coefficients[n];}
    Polynomial& operator=(Polynomial &p);
    friend void swap(Polynomial& first, Polynomial& second);
    void resize(int x);

    friend istream& operator>>(istream &is, Polynomial &p);
    friend ostream& operator<<(ostream &os, Polynomial &p);
};

这里是多项式.cpp

#include "Polynomial.h"

Polynomial::Polynomial(int length){
    this->length = length;
    coefficients = new int[length];
    for(int i = 0; i < length; i++){
        coefficients[i] = 0;
    }
}


Polynomial::~Polynomial(void){
    cout<<"Deleting: "<<coefficients;
    delete[] coefficients;
}
/*
Polynomial Polynomial::operator*(Polynomial p){
Polynomial temp(length + p.getLength());

for(int i = 0; i < length; i++){
for(int j = 0; j < length; j++){
temp[i+j] += coefficients[i] * p[j];            
}
}   
cout<<temp;
return temp;
}*/

Polynomial operator*(const Polynomial &p1,const Polynomial &p2){
    Polynomial temp(p1.getLength() + p2.getLength());

    for(int i = 0; i < p1.getLength(); i++){
        for(int j = 0; j < p2.getLength(); j++){
            temp[i+j] += p1[i] * p2[j];         
        }
    }   
    cout<<temp;
    return temp;
}
void Polynomial::resize(int x){
    delete[] coefficients;
    coefficients = new int[x];
}
void swap(Polynomial& first,Polynomial& second){
    int tempLength = first.getLength();
    int *temp = new int[tempLength];
    for(int i = 0; i < first.getLength(); i++)
        temp[i] = first[i];
    first.resize(second.getLength());
    for(int i = 0; i < first.getLength(); i++)
        first[i] = second[i];
    second.resize(tempLength);
    for(int i = 0; i < first.getLength(); i++)
        second[i] = temp[i];
    delete[]temp;
}
Polynomial& Polynomial::operator=(Polynomial &p){
    swap(*this,p);
    return *this;
}
Polynomial::Polynomial(const Polynomial &p){
    //if(coefficients) delete [] coefficients;
    coefficients = new int[p.getLength()];
    for(int i = 0; i < p.getLength(); i++)
        coefficients[i] = p[i];
}
istream& operator>>(istream &is,Polynomial &p){
    cout<<"Enter length: ";
    is>>p.length;
    p.coefficients = new int[p.length];
    for(int i = 0; i < p.length; i ++)
        is>>p.coefficients[i];  
    return is;
}
ostream& operator<<(ostream &os,Polynomial &p){ 
    for(int i = 0; i < p.length; i ++)
        if(p.coefficients[i])
            os<<p.coefficients[i]<<"x^"<<i<<" ";        
    return os;
}

这里是主要的

#include"Polynomial.h"

#include<iostream>
#include<string>

using namespace std;

int main(){
    Polynomial p1,p2,p3;
    cin>>p1>>p2;
    p3 = (p1 * p2);
    cout<<p3[0]<<p3[1]<<"here";
    cout<<p3;

    return 0;
}

编辑:这是最终更正的代码。我需要做的就是在所有构造函数中用 null 和 length 初始化指针。

#include "Polynomial.h"

Polynomial::Polynomial():
    coefficients(nullptr),length(0){}

Polynomial::Polynomial(int length):coefficients(nullptr),length(length){    
    coefficients = new int[length];
    for(int i = 0; i < length; i++){
        coefficients[i] = 0;
    }
}

Polynomial::~Polynomial(void){  
    if(coefficients) delete[]coefficients;
}

Polynomial& Polynomial::operator=(const Polynomial &p){ 
    if(coefficients)    
        delete[]coefficients;

    length = p.getLength();
    coefficients = new int[length]; 
    for(int i = 0; i < length; i++)
        coefficients[i] = p[i]; 

    return *this;
}
Polynomial::Polynomial(const Polynomial &p):
    coefficients(nullptr),length(0)
{   
    length = p.getLength();
    coefficients = new int[length]; 
    for(int i = 0; i < length; i++)
        coefficients[i] = p[i];     
}

Polynomial operator*(const Polynomial &p1,const Polynomial &p2){
    Polynomial temp(p1.getLength() + p2.getLength());
    for(int i = 0; i < p1.getLength(); i++)
        for(int j = 0; j < p2.getLength(); j++)
            temp[i+j] += p1[i] * p2[j];         
    return temp;
}

istream& operator>>(istream &is,Polynomial &p){
    cout<<"Enter length: ";
    is>>p.length;
    p.coefficients = new int[p.length];
    for(int i = 0; i < p.length; i ++)
        is>>p.coefficients[i];  
    return is;
}
ostream& operator<<(ostream &os,Polynomial &p){ 
    for(int i = 0; i < p.length; i ++)
        if(p.coefficients[i])
            os<<p.coefficients[i]<<"x^"<<i<<" ";        
    return os;
}

【问题讨论】:

  • 不确定这是如何编译的,因为operator= 需要一个引用,而(p1 * p2) 返回一个不会绑定到非常量引用的临时值。更改 operator*operator= 以获取 const refs 并尝试一下。 operator* 也不应该是成员函数。
  • 当您按值返回对象时,例如乘法运算符,会创建一个临时对象,然后将其销毁。你认为这个临时对象中的指针会发生什么?当临时对象被破坏时,它被复制然后删除。那么副本现在指向什么?你需要遵守rule of three
  • 你在谈论垃圾值,但这段代码甚至不应该编译error C2679: binary '=' : no operator found which takes a right-hand operand of type 'Polynomial'
  • GCC 不会编译这个。
  • 我正在使用视觉工作室。我不能在这两个函数中使用 const Polynomial&,正如 Visual Studio 所说,“没有运算符匹配这些操作数,操作数类型是 const Polynomial [int]”,我使用 p[i] 命令。

标签: c++


【解决方案1】:

如果您使用正确的标志调用编译器,则此代码不得编译。 没有可以很好地使用默认标志的 C++ 编译器!

例如,让我们以 Visual C++ 2013 为例,像这样调用,带有一些非默认标志:

cl /nologo /EHsc /Za /W4 stackoverflow.cpp

结果是编译器错误:

stackoverflow.cpp(78) : error C2679: binary '=' : no operator found which takes a right-hand operand of type 'Polynomial' (or there is no acceptable conversion)

        stackoverflow.cpp(19): could be 'void Polynomial::operator =(Polynomial &)'
        while trying to match the argument list '(Polynomial, Polynomial)'

现在让我们看看如果我们删除 /Za 标志会发生什么,它会禁用 Microsoft 扩展:

cl /nologo /EHsc /W4 stackoverflow.cpp

错误消失了。但是,会出现警告

warning C4239: nonstandard extension used : 'argument' : conversion from 'Polynomial' to 'Polynomial &'
        A non-const reference may only be bound to an lvalue; assigment operator takes a reference to non-const

最后,让我们看看如果我们删除警告级别标志/W4,编译器会做什么:

cl /nologo /EHsc stackoverflow.cpp

没有错误,没有警告。这不好p3 = (p1 * p2); 不是标准 C++。即使你可以让 Visual C++ 编译代码,也不会有什么好处。

赋值运算符的正确签名是:

Polynomial& Polynomial::operator=(Polynomial const& p);

然而,您的代码最大的问题是使用了new[]delete[]。你肯定在某个地方有内存泄漏(构造和赋值都分配,但只有析构函数删除),更糟糕的是,你多次删除同一个数组。

将以下输出添加到您的析构函数中:

Polynomial::~Polynomial(void){
    std::cout << "coefficients address destructor: " << coefficients << "\n";
    delete[] coefficients;
}

如果你现在运行它,你会看到类似:

Enter length: 2
100
200
Enter length: 3
100
200
300
10000x^0 40000x^1 40000x^2 coefficients address destructor: 00463BF8
coefficients address destructor: 00463BF8
1040000herecoefficients address destructor: 00463BF8

三次00463BF8!这是未定义的行为,可能会导致您的程序中发生所有可以想象的事情。

但这怎么可能呢?

答案是您的operator* 返回Polynomial 对象的浅表副本。里面的指针被复制了,你最终得到了两个指向同一个分配内存的指针。

您需要一个复制构造函数。而且你必须尊重所谓的三原则:如果你需要实现复制构造函数、复制赋值操作符和析构函数的一个,那么你需要实现它们所有 .

尽管如此,与其费力地手动实现动态分配及其无数的陷阱,不如帮自己一个忙,使用std::vector而不是指针和长度变量:

class Polynomial
{
private:
    std::vector<int> coefficients;
    // ...
};

您的下标运算符也需要修改。实际上,您需要其中两个,一个用于 const 访问,一个用于非常量访问:

inline int operator[] (int n) const { return coefficients[n];}
inline int& operator[] (int n) { return coefficients[n];}

why do subscript operators C++ often comes in pair?

最后,我想你误解了extern。你不需要它来使用friend 函数。只需将其从您的代码中删除;否则 GCC 甚至不会编译它。

这是一个包含最重要修复的完整示例:

#include <iostream>
#include <vector>

using namespace std;

class Polynomial
{
private:
    std::vector<int> coefficients;
public:
    inline int getLength() const {return coefficients.size();}
    Polynomial(int length);
    Polynomial(){}
    Polynomial operator*(Polynomial &p);
    inline int operator[] (int n) const { return coefficients[n];}
    inline int& operator[] (int n) { return coefficients[n];}

    friend istream& operator>>(istream &is, Polynomial &p);
    friend ostream& operator<<(ostream &os, Polynomial &p);
};

Polynomial::Polynomial(int length) :
    coefficients(length)
{
    for(int i = 0; i < length; i++){
        coefficients[i] = 0;
    }
}

Polynomial Polynomial::operator*(Polynomial &p){
    Polynomial temp(coefficients.size() + p.getLength());

    for(int i = 0; i < coefficients.size(); i++){
        for(int j = 0; j < coefficients.size(); j++){
            temp[i+j] += coefficients[i] * p[j];            
        }
    }   
    cout<<temp;
    return temp;
}

istream& operator>>(istream &is,Polynomial &p){
    cout<<"Enter length: ";
    int length;
    is>>length;
    p.coefficients.clear();
    p.coefficients.resize(length);
    for(int i = 0; i < length; i ++)
        is>>p.coefficients[i];  
    return is;
}
ostream& operator<<(ostream &os,Polynomial &p){ 
    for(int i = 0; i < p.coefficients.size(); i ++)
        if(p.coefficients[i])
            os<<p.coefficients[i]<<"x^"<<i<<" ";        
    return os;
}

int main(){
    Polynomial p1,p2,p3;
    cin>>p1>>p2;
    p3 = (p1 * p2);

    cout<<p3[0]<<p3[1]<<"here";
    cout<<p3;

    return 0;
}

您不再需要自己编写的复制构造函数、复制赋值运算符或析构函数,因为std::vector 知道如何以安全的方式复制、分配和破坏自身,而不会出现低级内存问题。

未来改进的更多示例:

  • 检查std::cin 是否确实收到了有效整数。
  • 根据operator*= 实现operator*
  • operator* 引用const
  • 不要在头文件的全局范围内使用using namespace

通常,请阅读const 和运算符重载的最佳实践。

【讨论】:

  • 我知道矢量。出于学校的原因,我故意避免它。是的 cin 收到了有效的整数,它只是复制不正确。
  • @SouradeepNanda:那么,让它正确复制。谷歌“复制交换成语”。注意new[]delete[] 只是表面上对应std::vector 在里面所做的事情。 std::vector 的默认分配器使用placement new,但这对于初学者来说可能太高级了。而且您通常需要检查有效的用户输入。如果您输入“xyz”而不是数字,您会如何看待刚刚崩溃的程序?
【解决方案2】:

假设它可以编译。

您没有复制构造函数,这不是一个好主意。如果您实现析构函数,您可能需要分配-复制-交换-移动功能。 What is The Rule of Three?(半)

另外,最好使用std::vector 而不是动态分配,因为它会自动实现这些行为,编译器生成的构造函数就足够了。

在您的情况下,返回会创建一个浅拷贝(自动生成的 copy-cstr),但在函数返回后,原始拷贝会被销毁——它已经分配了浅拷贝引用的内存——并释放了内存。

这就够了:

class Polynomial
{
private:
    std::vector<int> coefficients;

public:
    inline int getLength() const { return coefficients.size(); };
    Polynomial(int length): coefficients(length, 0) {};
    Polynomial operator*(const Polynomial& p); //you are not modifying p, thus const ref can work

    extern friend istream& operator>>(istream &is, Polynomial &p);
    extern friend ostream& operator<<(ostream &os, Polynomial &p);
};

或者,您可以使用数组解决方案,并使用所有必要的东西正确地实现它:

class Polynomial
    {
    public:
        Polynomial(int length);
        ~Polynomial();
        Polynomial(const Polynomial& other);
        friend void swap(Polynomial& first, Polynomial& second) // nothrow
        Polynomial& operator=(Polynomial other)
        {
            swap(*this, other);
            return *this;
        }

        int getLength();
        Polynomial operator*(const Polynomial& p); //you are not modifying p, thus const ref can work

        extern friend istream& operator>>(istream &is, Polynomial &p);
        extern friend ostream& operator<<(ostream &os, const Polynomial &p);
    };

这是copy swap idiom

编辑:你也需要inline int operator[](int n) const { return coefficients[n];}

【讨论】:

  • 我创建了自己的复制构造函数,问题仍然存在。我知道我总是可以使用向量,但在学校里他们仍然使用裸指针和动态数组。
  • 已编辑,也添加了一个 const 版本,也尝试使用我定义的签名进行创建。
猜你喜欢
  • 2015-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-18
  • 2019-11-06
  • 1970-01-01
  • 2017-02-07
  • 1970-01-01
相关资源
最近更新 更多