【问题标题】:C++ : Implementing copy constructor and copy assignment operatorC++:实现复制构造函数和复制赋值运算符
【发布时间】:2011-06-09 17:11:02
【问题描述】:

在阅读了 C++ 中的复制构造函数和复制赋值运算符之后,我尝试创建一个简单的示例。虽然下面的 sn-p 显然有效,但我不确定我是否以正确的方式实现了复制构造函数和复制赋值运算符。您能否指出是否有任何错误/改进或更好的示例来理解相关概念。

class Foobase
{
    int bInt;

public:
    Foobase() {}

    Foobase(int b) { bInt = b;}

    int GetValue() { return bInt;}

    int SetValue(const int& val) { bInt = val; }
};


class Foobar
{
    int var;    
    Foobase *base;      

public:
    Foobar(){}

    Foobar(int v)
    {
        var = v;        
        base = new Foobase(v * -1);

    }

    //Copy constructor
    Foobar(const Foobar& foo)
    {       
        var = foo.var;
        base = new Foobase(foo.GetBaseValue());
    }

    //Copy assignemnt operator
    Foobar& operator= (const Foobar& other)
    {
        if (this != &other) // prevent self-assignment
        {
            var = other.var;
            base = new Foobase(other.GetBaseValue());

        }
        return *this;
    }

    ~Foobar()
    {
        delete base;
    }

    void SetValue(int val)
    {
        var = val;
    }

    void SetBaseValue(const int& val)
    {
        base->SetValue(val);
    }

    int GetBaseValue() const
    {
        return(base->GetValue());
    }

    void Print()
    {
        cout<<"Foobar Value: "<<var<<endl;
        cout<<"Foobase Value: "<<base->GetValue()<<endl;

    }   

};

int main()
{
    Foobar f(10);       
    Foobar g(f);  //calls copy constructor
    Foobar h = f; //calls copy constructor

    Foobar i;
    i = f;

    f.SetBaseValue(12);
    f.SetValue(2);    

    Foobar j = f = z; //copy constructor for j but assignment operator for f

    z.SetBaseValue(777);
    z.SetValue(77);

    return 1;
}

【问题讨论】:

    标签: c++ copy-constructor


    【解决方案1】:

    您的复制赋值运算符实现不正确。分配给的对象会泄漏其base 指向的对象。

    您的默认构造函数也不正确:它使base 和var 都未初始化,因此无法知道其中任何一个是否有效并且在析构函数中,当您调用delete base; 时,会发生坏事。

    实现复制构造函数和复制赋值操作符的最简单方法是使用the Copy-and-Swap idiom。

    【讨论】:

    • +1 好点复制和交换习惯用法对于保证对象保持一致状态的异常安全实现至关重要。
    • 析构函数~Foobar()不会释放内存吗?
    • @Mahatma: ~Foobar() 不会被调用;该对象永远不会被销毁,它被分配给。
    • @Mahatma 如果你有一个垃圾收集器,将调用析构函数,在这种情况下,析构函数将在某些非必要确定的时刻被调用,或者如果该指针将是一个智能指针。但是您没有垃圾收集器,并且指针肯定不聪明。所以你必须手动清理。当然,或者使用智能指针,这是一个绝妙的主意。
    • 好吧,因为我做了一个base = new Foobase(other.GetBaseValue());,原来的内存区域base指向了泄漏。但是,如果我在新分配之前执行delete base; 以释放原始内存,则会收到double free or corruption 错误。
    【解决方案2】:

    只有Foobar 需要自定义复制构造函数、赋值运算符和析构函数。 Foobase 不需要,因为编译器提供的默认行为已经足够好。

    在Foobar 的情况下,赋值运算符有泄漏。您可以通过在分配对象之前释放对象来轻松修复它,这应该足够好了。但是,如果您将 second 指针成员添加到 Foobar,您会发现事情变得复杂了。现在,如果在分配第二个指针时出现异常,则需要正确清理分配的第一个指针,以避免损坏或泄漏。当您添加更多指针成员时,事情会变得比多项式方式更复杂。

    相反,您要做的是根据复制构造函数实现赋值运算符。然后,您应该根据非抛出交换函数来实现复制构造函数。有关详细信息,请阅读 Copy & Swap 习语。

    另外,Foobar 的默认构造函数不会默认初始化成员。这很糟糕,因为这不是用户所期望的。成员指针指向任意地址,int 具有任意值。现在,如果您使用构造函数创建的对象,您就非常接近未定义行为领域。

    【讨论】:

    • 如果我在新分配之前执行delete base; 以释放原始内存,则会收到double free or corruption 错误。我会研究成语。我没有得到关于空默认构造函数问题的最后一部分。我是否应该将var = SOMEVALUE; 和base = NULL; 放在那里,因为在我的示例中,我使用了参数化构造函数。
    • new-expression 中没有括号只会对没有构造函数的类型产生影响,而这里没有任何类型。具有至少一个用户定义构造函数的类型的默认初始化会生成对默认构造函数的调用。
    • 学究式地,默认构造函数确实 default-initialize 成员(由于没有 ctor-initializer,因此在任何 mem-initializer-id,并且标准指定它们在这种情况下是默认初始化)。但是int 的默认初始化未指定状态。
    【解决方案3】:

    我有一个非常简单的补丁给你:

    class Foobar
    {
      int var;    
      std::unique_ptr<FooBase> base;
    
    ...
    

    这应该让你开始。

    底线是:

    1. 不要在代码中调用delete(专家见第2点)
    2. 不要在你的代码中调用delete(你知道得更好......)

    【讨论】:

    • 根据编译器提供的C++库的年龄,可能是std::tr1::unique_ptr...
    猜你喜欢
    • 2012-06-27
    • 2011-07-19
    • 1970-01-01
    • 2011-05-19
    • 2013-09-28
    • 1970-01-01
    • 1970-01-01
    • 2020-06-13
    相关资源
    最近更新 更多