此C ++代码包含哪些未定义的行为

时间:2009-07-27 11:37:40

标签: c++ pointers

我在阅读了有效C ++(第三版)的第11项后写了这段代码。

#include <iostream>
using namespace std;

#define MAX_COLORS 20
class Widget
{
 public:
    Widget ( int seed );
    ~Widget ( );
    Widget& operator=( const Widget& rhs );
    void ToString ( );
 private:

    Widget& SelfAssignmentUnsafe ( const Widget& rhs );
    Widget& SelfAssignmentSafe ( const Widget& rhs );
    Widget& SelfAssignmentAndExceptionSafe ( const Widget& rhs );
    void MakeDeepCopy ( const Widget& rhs );
    int *colorPallete;
};

void Widget::ToString()
{
 int i = 0;
 for ( i = 0; i < MAX_COLORS; i++ )
 {
 cout << "colorPallete[" << i << "]: " << colorPallete[i] << endl;
 }
}

Widget::Widget ( int seed ):
    colorPallete ( new int[MAX_COLORS])
    {
     int i = 0;
     for ( i = 0; i < MAX_COLORS; i++ )
     {
      colorPallete[i] = seed + i;
     }
    }

Widget& Widget::operator=( const Widget& rhs )
{
//    return SelfAssignmentUnsafe ( rhs );

//    return SelfAssignmentSafe( rhs ); 

    return SelfAssignmentAndExceptionSafe ( rhs );
}

Widget& Widget::SelfAssignmentUnsafe ( const Widget& rhs )
{
    delete[] colorPallete;
    colorPallete = 0;
    MakeDeepCopy( rhs );
    return *this;
}

Widget& Widget::SelfAssignmentSafe ( const Widget& rhs )
{
    if ( this == &rhs ) return *this;

    delete[] colorPallete;
    colorPallete = 0;
    MakeDeepCopy ( rhs );
    return *this;
}

void Widget::MakeDeepCopy ( const Widget& rhs )
{
    int i = 0;
    colorPallete = new int [MAX_COLORS];
    for ( i = 0;i < MAX_COLORS; i++ )
    {
     colorPallete[i] = rhs.colorPallete[i];
    }
}

Widget& Widget::SelfAssignmentAndExceptionSafe ( const Widget& rhs )
{
    int *origColorPallete = colorPallete;
    MakeDeepCopy ( rhs );
    delete[] origColorPallete;
    origColorPallete = 0;
    return *this;    
}

Widget::~Widget()
{
 delete[] colorPallete;
}    


int main()
{
 Widget b(10);
 Widget a(20);
 b.ToString();
 b = b; 
 cout << endl << "After: " << endl;
 b.ToString();
}

作者谈到在赋值运算符中处理对self的赋值:

Widget a(10);
a = a;

从Widget的赋值运算符中,我调用 Widget :: SelfAssignmentAndExceptionSafe。

Widget :: SelfAssignmentAndExceptionSafe 中,我们的想法是将colorPallete指针保存在origColorPallete中。然后制作rhs.colorPallete的深层副本。当复制成功时,我删除原始指针并返回对self的引用。

上述机制应该是自我分配和异常安全。

但是, Widget :: SelfAssignmentAndExceptionSafe 无法正确处理自我分配。 colorPallete数组在自我赋值后包含垃圾。它很好地处理其他情况。

为什么会这样?

请帮忙。

[编辑:检查完所有答案后]

感谢您的回答。我已经更新了MakeDeepCopy方法,并且示例现在正常工作。下面,我已粘贴更新的代码:

#include <iostream>

using namespace std;

#define MAX_COLORS 20
class Widget
{
 public:
    Widget ( int seed );
    ~Widget ( );
    Widget& operator=( const Widget& rhs );
    void ToString ( );
 private:
    Widget( Widget& rhs );
    Widget& SelfAssignmentUnsafe ( const Widget& rhs );
    Widget& SelfAssignmentSafe ( const Widget& rhs );
    Widget& SelfAssignmentAndExceptionSafe ( const Widget& rhs );
    void MakeDeepCopy ( const int* rhs );
    int *colorPallete;
};

void Widget::ToString()
{
 int i = 0;
 for ( i = 0; i < MAX_COLORS; i++ )
 {
 cout << "colorPallete[" << i << "]: " << colorPallete[i] << endl;
 }
}

Widget::Widget ( int seed ):
    colorPallete ( new int[MAX_COLORS])
    {
     int i = 0;
     for ( i = 0; i < MAX_COLORS; i++ )
     {
      colorPallete[i] = seed + i;
     }
    }

Widget& Widget::operator=( const Widget& rhs )
{
//    return SelfAssignmentUnsafe ( rhs );

//    return SelfAssignmentSafe( rhs ); 

    return SelfAssignmentAndExceptionSafe ( rhs );
}

Widget& Widget::SelfAssignmentUnsafe ( const Widget& rhs )
{
    delete[] colorPallete;
    colorPallete = 0;
    MakeDeepCopy( rhs.colorPallete );
    return *this;
}

Widget& Widget::SelfAssignmentSafe ( const Widget& rhs )
{
    if ( this == &rhs ) return *this;

    delete[] colorPallete;
    colorPallete = 0;
    MakeDeepCopy ( rhs.colorPallete );
    return *this;
}

void Widget::MakeDeepCopy ( const int* rhs )
{
    int i = 0;
    colorPallete = new int [MAX_COLORS];
    for ( i = 0;i < MAX_COLORS; i++ )
    {
     colorPallete[i] = rhs[i];
    }
}

Widget& Widget::SelfAssignmentAndExceptionSafe ( const Widget& rhs )
{
    int *origColorPallete = colorPallete;
    MakeDeepCopy ( rhs.colorPallete );
    delete[] origColorPallete;
    origColorPallete = 0;
    return *this;    
}

Widget::~Widget()
{
 delete[] colorPallete;
}    


int main()
{
 Widget b(10);
 Widget a(20);
 b.ToString();
 b = b; 
 cout << endl << "After: " << endl;
 b.ToString();
}

[编辑:基于Charles的回复的修改代码]

这个想法是实现“复制和交换”的习惯用法,使代码既可以自我赋值也可以安全。请注意,复制仅在复制构造函数中实现。如果复制成功,我们交换赋值运算符。

与之前的更新相比,另一项改进是MakeDeepCopy的界面取决于正确的用法。我们必须在调用MakeDeepCopy之前存储/删除colorPallete指针。现在不存在这样的依赖。

#include <iostream>

using namespace std;

#define MAX_COLORS 20
class Widget
{
 public:
    Widget ( int seed );
    ~Widget ( );
    Widget& operator=( const Widget& rhs );
    void ToString ( );
    Widget( const Widget& rhs );
 private:
    int *colorPallete;
};

void Widget::ToString()
{
 int i = 0;
 for ( i = 0; i < MAX_COLORS; i++ )
 {
 cout << "colorPallete[" << i << "]: " << colorPallete[i] << endl;
 }
}

Widget::Widget ( int seed ):
    colorPallete ( new int[MAX_COLORS])
    {
     int i = 0;
     for ( i = 0; i < MAX_COLORS; i++ )
     {
      colorPallete[i] = seed + i;
     }
    }

Widget::Widget( const Widget& rhs ):
    colorPallete( new int[MAX_COLORS] )
{
    std::copy ( rhs.colorPallete, rhs.colorPallete + MAX_COLORS, colorPallete );
}

Widget& Widget::operator=( const Widget& rhs )
{
    Widget tmp(rhs);

    std::swap ( colorPallete, tmp.colorPallete );   

    return *this; 
}

Widget::~Widget()
{
 delete[] colorPallete;
}    


int main()
{
 Widget b(10);
 Widget a(20);
 b.ToString();
 b = b; 
 cout << endl << "After: " << endl;
 b.ToString();
}

5 个答案:

答案 0 :(得分:1)

您看到的垃圾是因为MakeDeepCopy功能始终从colorPallete的{​​{1}}成员复制,而不是您在rhs中创建的副本。

以下修改将解决它:

origColorPallete

实际上,通过上述修改,您可能需要将int *Widget::MakeDeepCopy ( const int *rhs ) { int i = 0; int *colorPallete = new int [MAX_COLORS]; for ( i = 0;i < MAX_COLORS; i++ ) { colorPallete[i] = rhs[i]; } return colorPallete; } Widget& Widget::SelfAssignmentAndExceptionSafe ( const Widget& rhs ) { int *origColorPallete = colorPallete; colorPallete = MakeDeepCopy ( origColorPallete ); delete[] origColorPallete; origColorPallete = 0; return *this; } 重命名为MakeDeepCopy或其他内容(特别是如果您希望将原始CopyColorPalette保留用于其他目的)。

答案 1 :(得分:1)

当您调用MakeDeepCopy时,您始终会传入对象的引用。因此它再次作为自我分配运行。

如果在每个公共方法中检查自我赋值,并且只有在传递了另一个对象的情况下调用赋值时才会运行复制,那将会好得多。

答案 2 :(得分:1)

通过简单地使用std :: vector而不是动态分配的数组,可以避免很多麻烦。向量支持赋值(包括自我赋值),因此没有什么可以做的。

答案 3 :(得分:1)

问题是你没有处理复制本身。 因此,当您在自身上执行复制时,语句

colorPallete = new int [MAX_COLORS];

实际上也覆盖了rhs的colorPallete

答案 4 :(得分:0)

在你的例子中像一个痛苦的拇指一样伸出来就是缺少用户定义的复制构造函数。当您提供用户定义的析构函数和赋值运算符时,可以合理地推断出您可能需要一个用户定义的复制构造函数,这确实就是这里的情况。任何对编译器生成的复制构造函数的显式或隐式调用都会在原始文件和副本的最后一个被销毁时导致未定义的行为。

您可以为您的类编写一个简单的 no-throw 交换函数,编写异常中立复制构造函数相当容易。 (实际上,我认为编写并且相当容易推理它是异常中立的是微不足道的。)如果你根据这两个函数(复制和交换习语)实现你的赋值运算符,你会发现它更容易。特别是,你应该发现任何自我指派检查的需要都应该消失。

修改

自更新以来,您已使Widget赋值运算符异常安全。但是,您的设计取决于您在赋值操作中只有一个操作可能抛出(新内存的分配),因为int s的赋值不能抛出。一般来说,如果你持有一个对象数组,那就无法控制。

我知道MakeDeepCopy是一个私有函数,但即便如此,它有一个接口,在很大程度上取决于正确的用法。成员变量colorPallete必须为delete[]并设置为0,或者在调用成功的情况下必须将其保存到临时变量,以便可以delete[]编辑。

即使您不想公开复制构造函数,我仍然会使用它来实现赋值运算符,因为它使整个代码更简单。

E.g。

Widget::Widget( const Widget& rhs )
    : colorPallete( new int[MAX_COLORS] )
{
    // OK because assigning ints won't through
    std::copy( rhs.colorPallete, rhs.colorPallete + MAX_COLORS. colorPallete );
}

Widget& Widget::operator=( const Widget& rhs )
{
    // Try allocating a copy, Widget's copy constructor must
    // leak anything if it throws

    Widget tmp( rhs );

    // If that worked, swap with the copy - this can't throw

    std::swap( colorPallete, tmp.colorPallete );

    // Our old internals are now part of tmp so will be
    // deallocated by tmp's destructor
}

我在复制构造函数中有效地使用了MakeDeepCopy,但在调用代码上没有任何必要条件,因为它是一个复制构造函数和一个简单的两行赋值运算符(IMHO)更明显例外安全。

请注意,如果您持有可能在分配期间抛出的对象数组,则必须做一些更聪明的事情来维护异常安全性和透明度。例如(这可能说明了为什么使用std::vector是一个好主意):

template< class T  >
class PartialArrayDeleter
{
public:
    PartialArrayDeleter( T* p )
        : p_( p ) {}

    ~PartialArrayDeleter()
    {
        delete[] p_;
    }

    void reset()
    {
        p_ = 0;
    }

private:
    T* p_;
};

Widget::Widget( const Widget& rhs )
    : colorPallete( new Obj[MAX_COLORS] )
{
    PartialArrayDeleter<Obj> del( colorPallete );

    std::copy( rhs.colorPallete, rhs.colorPallete + MAX_COLORS. colorPallete );

    del.reset();
}

编辑2:

如果您认为考虑分配int以外的对象无关紧要,请注意,如果您只考虑所拥有的类,则在分配期间不必严格重新分配。所有小部件在其构造函数中分配的内存量相同。一个简单,高效且异常安全的赋值运算符是:

Widget& Widget::operator=( const Widget& rhs )
{
    for( size_t i = 0; i != MAX_COLORS; ++i )
    {
        colorPallete[i] = rhs.colorPallete[i];
    }
    return *this;
}

int s的自我分配是安全的,并且如前所述,int的分配也是例外安全的。 (我不是100%肯定,但我不认为std::copy在技术上保证对于自我分配副本是安全的。)