班级成员在复制构造或按作业复制后出现乱码(有时)

Class members garbled after copy-construction or copy by assignment (sometimes)

提问人:synaptik 提问时间:9/27/2013 最后编辑:synaptik 更新时间:9/27/2013 访问量:131

问:

我的类表示一个正态分布的随机变量。默认情况下,实例正态分布,均值为 0 和 stdev 1(即标准正态随机变量)。NRRanNormal

有时,当我复制对象时,复制到(或通过复制构造函数构造)的对象的平均值和标准是乱码和无意义的。我很难找到这种乱码的原因。NRRanNormal

出于测试目的,以下函数显示给定对象的平均值和 stdev:NRRanNormal

void go(NRRanNormal& rv, const string label) {
    std::cout << label     << "\n"
              << "Mean:  " << rv.getMean()  << "\n"
              << "Stdev: " << rv.getStdev() << "\n\n";
}

现在,让我们看看在以下 4 种情况下会发生什么:

NRRanNormal foo;
go(foo, "foo");

NRRanNormal bar1 = foo;
go(bar1, "bar1");

NRRanNormal bar2;
bar2 = foo;
go(bar2, "bar2");

NRRanNormal bar3(foo);
go(bar3, "bar3");

上述语句的输出如下:

foo
Mean:  0
Stdev: 1

bar1
Mean:  5.55633e-317
Stdev: 6.95332e-310

bar2
Mean:  0
Stdev: 1

bar3
Mean:  0
Stdev: 0

正如你所看到的,简单地实例化一个对象()就可以按预期工作。foo

现在,当我这样做时,对象是乱码。但是,当我这样做时,对象不会乱码。这让我感到困惑。我以为像这样的语句块NRRanNormal bar1 = foo;bar1NRRanNormal bar2; bar2 = foo;bar2

MyClass A;
MyClass B = A;

实际上由编译器转换为语句块

MyClass A;
MyClass B;
B = A;

因此,除非我刚才在上面写的是不正确的,否则似乎并且应该具有完全相同的成员值。但是,正如您从上面粘贴的输出中看到的那样,是乱码,而很好。bar1bar2bar1bar2

这怎么可能?

您还会注意到这是乱码。我不确定这是同一个问题,还是另一个问题。bar3


下面是接口和实现的简化版本:NRRanNormal

class NRRanNormal {
public:

    NRRanNormal();

    ~NRRanNormal();

    NRRanNormal(const NRRanNormal& nrran);

    NRRanNormal& operator= (const NRRanNormal& nrran);

    double getMean()  const;    
    double getStdev() const;    
    long   getSeed()  const;

private:    
    double m_mean, m_stdev;     
    long m_seed;    
    Normaldev* stream; // underlying C struct RN generator

};

NRRanNormal::NRRanNormal() { // by default, N(0,1)
    m_mean  = 0.0;
    m_stdev = 1.0;
    m_seed  = 12345L;
    stream  = new Normaldev(m_mean, m_stdev, m_seed);
}

NRRanNormal::~NRRanNormal() { delete stream; }

NRRanNormal::NRRanNormal(const NRRanNormal& nrran) {
    stream = new Normaldev(nrran.getMean(),nrran.getStdev(),nrran.getSeed());
    *stream = *(nrran.stream);
}

NRRanNormal& NRRanNormal::operator= (const NRRanNormal& nrran) {            
    if(this == &nrran)
        return *this;

    delete stream;
    stream = new Normaldev(nrran.getMean(),nrran.getStdev(),nrran.getSeed());
    *stream = *(nrran.stream);

    return *this;
}

double NRRanNormal::getMean()  const { return m_mean; }    
double NRRanNormal::getStdev() const { return m_stdev; }    
long   NRRanNormal::getSeed()  const { return m_seed; }

该结构来自 Numerical Recipes 3d Edition。Normaldev

我的复制赋值运算符或复制构造函数有问题吗?


这里是,剥离了专有计算。Normaldev

typedef double Doub;        
typedef unsigned long long int Ullong;
typedef unsigned int Uint;

struct Ranq1 {
    Ullong v;
    Ranq1(Ullong j) : v(/* some long number here */) {
        /* proprietary calculations here */
    }
    inline Ullong int64() {
        /* proprietary calculations here */
    }
    inline Doub doub() { /* proprietary calculations here */ }
    inline Uint int32() { return (Uint)int64(); }
};

struct Normaldev : Ranq1 {
    Doub mu,sig;
    Normaldev(Doub mmu, Doub ssig, Ullong i):
    Ranq1(i), mu(mmu), sig(ssig){}
    Doub dev() {
        /* proprietary calculations here */
    }
};
C++ 复制构造函数 Rule-of-Three copy-assignment

评论

0赞 syam 9/27/2013
顺便说一句,你的赋值运算符并不是特别安全的:如果抛出,你最终会删除几次。您可能希望首先(使用临时指针),并且仅在成功时才这样做。但是你真的需要动态分配吗?至少使用智能指针来帮助实现异常安全。newstreamnewdeletestream

答:

4赞 john 9/27/2013 #1

这是你的问题

NRRanNormal::NRRanNormal(const NRRanNormal& nrran) {
    stream = new Normaldev(nrran.getMean(),nrran.getStdev(),nrran.getSeed());
    *stream = *(nrran.stream);
}

应该是

NRRanNormal::NRRanNormal(const NRRanNormal& nrran) : 
    m_mean(nrran.m_mean), 
    m_stdev(nrran.m_stdev), 
    m_seed(nrran.m_seed)
{
    stream = new Normaldev(nrran.getMean(),nrran.getStdev(),nrran.getSeed());
    *stream = *(nrran.stream);
}

复制构造函数无法复制 mean、stddev 和 seed。您的赋值运算符有同样的问题

NRRanNormal& NRRanNormal::operator= (const NRRanNormal& nrran) {            
    if(this == &nrran)
        return *this;

    m_mean  = nrran.m_mean;
    m_stdev = nrran.m_stdev;
    m_seed  = nrran.m_seed;
    delete stream;
    stream = new Normaldev(nrran.getMean(),nrran.getStdev(),nrran.getSeed());
    *stream = *(nrran.stream);

    return *this;
}

我猜你太专注于课堂上棘手的指针,以至于忘记了基本的东西。

顺便说一句代码

MyClass A;
MyClass B = A;

实际上被编译器转换为

MyClass A;
MyClass B(A);

换句话说,调用复制构造函数(假设 A 和 B 是同一类型)。MyClass B = A;

评论

0赞 synaptik 9/27/2013
天哪,我!你是对的,当然,是基本的东西让我来到这里。多谢。另外,很高兴了解编译器转换说明。
0赞 synaptik 9/27/2013
而且,在更正后的版本中,更改为 .不过没什么大不了的。stream = new Normaldev(nrran.getMean(),nrran.getStdev(),nrran.getSeed());stream = new Normaldev(m_mean,m_stdev,m_seed);