2008-11-01 72 views
51

这是怎样的一个初学者问题的数组,但我没有做过C++在很长一段时间,所以这里去...动态分配对象

我有一个包含一个动态分配的数组类,说

class A 
{ 
    int* myArray; 
    A() 
    { 
     myArray = 0; 
    } 
    A(int size) 
    { 
     myArray = new int[size]; 
    } 
    ~A() 
    { 
     // Note that as per MikeB's helpful style critique, no need to check against 0. 
     delete [] myArray; 
    } 
} 

但现在我想创建一个动态分配这些类的数组。这是我目前的代码:

A* arrayOfAs = new A[5]; 
for (int i = 0; i < 5; ++i) 
{ 
    arrayOfAs[i] = A(3); 
} 

但是这个爆炸非常严重。由于在for循环迭代完成时创建的新对象A(使用A(3)调用)遭到破坏,这意味着该实例的内部myArray得到了delete [] -ed。

所以我认为我的语法一定是非常错误的?我想有几个修复看起来像矫枉过正,我希望避免:

  • A创建复制构造函数。
  • 使用vector<int>vector<A>所以我不必担心这一切。
  • 而不是arrayOfAsA对象的数组,请将其指定为A*指针的数组。

我会认为这只是一些初学者的事情,当尝试动态分配一组具有内部动态分配的事物时,实际上有一种语法。

(另外,风格的批评赞赏,因为它已经,因为我做了C++一段时间。)

更新未来观众:以下所有的答案都非常有帮助。由于示例代码和有用的“4规则”,Martin's被接受,但我真的建议阅读它们。有些是很好的,简短的说错了什么,有些正确指出如何以及为什么是一个好的方法。

回答

115

对于构建容器,您显然希望使用其中一个标准容器(如std :: vector)。但是当你的对象包含RAW指针时,这是你需要考虑的事情的完美例子。

如果你的对象有一个RAW指针,那么你需要记住3的规则(现在是C++ 11中的5的规则)。

  • 构造
  • 析构
  • 拷贝构造
  • 赋值运算符
  • 移动构造函数(C++ 11)
  • 移动分配(C++ 11)

这是因为如果没有定义,编译器将生成它自己的这些方法的版本(见下文)。编译器生成的版本在处理RAW指针时并不总是有用的。

复制构造函数是很难得到正确的(如果您想提供强大的异常保证,它是不平凡的)。 Assignment操作符可以根据Copy构造函数定义,因为您可以在内部使用复制和交换习语。

有关包含指向整数数组的指针的类的绝对最小值的详细信息,请参见下文。

知道确保正确无误,应该考虑使用std :: vector而不是指向整数数组的指针。该向量易于使用(并扩展)并涵盖与例外相关的所有问题。将下面的类与下面的A的定义进行比较。

class A 
{ 
    std::vector<int> mArray; 
    public: 
     A(){} 
     A(size_t s) :mArray(s) {} 
}; 

看到你的问题:

A* arrayOfAs = new A[5]; 
for (int i = 0; i < 5; ++i) 
{ 
    // As you surmised the problem is on this line. 
    arrayOfAs[i] = A(3); 

    // What is happening: 
    // 1) A(3) Build your A object (fine) 
    // 2) A::operator=(A const&) is called to assign the value 
    // onto the result of the array access. Because you did 
    // not define this operator the compiler generated one is 
    // used. 
} 

编译器生成的赋值运算符是罚款,几乎所有的情况,但是当RAW指针在发挥你需要注意。在你的情况下,它是由于浅拷贝问题导致问题。你已经结束了两个包含指向同一块内存的指针的对象。当A(3)在循环结束时超出范围时,它会在其指针上调用delete []。因此,另一个对象(在数组中)现在包含一个指向已被返回到系统的内存的指针。

编译器生成的拷贝构造函数;通过使用该成员复制构造函数来复制每个成员变量。对于指针而言,这仅仅意味着指针值从源对象复制到目标对象(因此是浅拷贝)。

编译器生成赋值运算符;通过使用该成员赋值运算符来复制每个成员变量。对于指针而言,这仅仅意味着指针值从源对象复制到目标对象(因此是浅拷贝)。

所以最低为包含指针类:

class A 
{ 
    size_t  mSize; 
    int*  mArray; 
    public: 
     // Simple constructor/destructor are obvious. 
     A(size_t s = 0) {mSize=s;mArray = new int[mSize];} 
     ~A()    {delete [] mArray;} 

     // Copy constructor needs more work 
     A(A const& copy) 
     { 
      mSize = copy.mSize; 
      mArray = new int[copy.mSize]; 

      // Don't need to worry about copying integers. 
      // But if the object has a copy constructor then 
      // it would also need to worry about throws from the copy constructor. 
      std::copy(&copy.mArray[0],&copy.mArray[c.mSize],mArray); 

     } 

     // Define assignment operator in terms of the copy constructor 
     // Modified: There is a slight twist to the copy swap idiom, that you can 
     //   Remove the manual copy made by passing the rhs by value thus 
     //   providing an implicit copy generated by the compiler. 
     A& operator=(A rhs) // Pass by value (thus generating a copy) 
     { 
      rhs.swap(*this); // Now swap data with the copy. 
           // The rhs parameter will delete the array when it 
           // goes out of scope at the end of the function 
      return *this; 
     } 
     void swap(A& s) noexcept 
     { 
      using std::swap; 
      swap(this.mArray,s.mArray); 
      swap(this.mSize ,s.mSize); 
     } 

     // C++11 
     A(A&& src) noexcept 
      : mSize(0) 
      , mArray(NULL) 
     { 
      src.swap(*this); 
     } 
     A& operator=(A&& src) noexcept 
     { 
      src.swap(*this);  // You are moving the state of the src object 
            // into this one. The state of the src object 
            // after the move must be valid but indeterminate. 
            // 
            // The easiest way to do this is to swap the states 
            // of the two objects. 
            // 
            // Note: Doing any operation on src after a move 
            // is risky (apart from destroy) until you put it 
            // into a specific state. Your object should have 
            // appropriate methods for this. 
            // 
            // Example: Assignment (operator = should work). 
            //   std::vector() has clear() which sets 
            //   a specific state without needing to 
            //   know the current state. 
      return *this; 
     } 
} 
4
  1. 仅当对象具有默认构造函数和复制构造函数时才使用数组或公共容器。

  2. 否则存储指针(或智能指针,但在这种情况下可能会遇到一些问题)。

PS:始终定义自己的默认和拷贝构造函数,否则自动生成将用于

10

我建议你使用std :: vector的:像

typedef std::vector<int> A; 
typedef std::vector<A> AS; 

有什么错STL的微小矫枉过正,你可以花更多的时间来实现你的应用程序的特定功能,而不是重新发明自行车。

2

你需要一个赋值操作符,因此:

arrayOfAs[i] = A(3); 

的作品,因为它应该。

+0

其实这个使用赋值运算符而不是拷贝构造函数。左手边已经完全建成。 – 2008-11-01 16:47:14

+0

哎呀,脑屁。感谢您的支持。固定。 – 2008-11-01 21:32:32

+0

不幸的不是。因为原始A(3)和arrayofAs [i]都包含指向堆上同一区域的成员myArray。第一个超出范围将删除该对象。第二个超出范围也会删除它,这会导致问题。 – 2008-12-13 20:33:57

6

您的A对象的构造函数动态分配另一个对象,并在原始指针中存储指向该动态分配对象的指针。

对于这种情况,您需要必须定义您自己的拷贝构造函数,赋值运算符和析构函数。编译器生成的将无法正常工作。 (这是“三巨头法则”的必然结果:具有任何析构函数,赋值运算符,拷贝构造函数的类通常都需要3)。

你已经定义了你自己的析构函数(并且你提到了创建一个拷贝构造函数),但是你需要定义其他两个大三。

另一种方法是将指针存储在动态分配的int[]的某个其他对象中,以便为您处理这些事情。就像vector<int>(如你所说)或boost::shared_array<>

为了解决这个问题 - 为了充分利用RAII,您应该尽可能避免处理原始指针。

既然你问了其他风格的批评,一个小问题是,当你删除原始指针时,你不需要检查0之前拨打delete - delete处理该案件,因为你不必你用检查代码混乱。

2

为什么不能有一个setSize方法相似。

A* arrayOfAs = new A[5]; 
for (int i = 0; i < 5; ++i) 
{ 
    arrayOfAs[i].SetSize(3); 
} 

我喜欢“复制”,但在这种情况下,默认的构造函数并没有真正做任何事情。 SetSize可以将数据从原始m_array中复制出来(如果存在的话)..您必须在类中存储数组的大小才能完成此操作。

SetSize可能会删除原始的m_array。

void SetSize(unsigned int p_newSize) 
{ 
    //I don't care if it's null because delete is smart enough to deal with that. 
    delete myArray; 
    myArray = new int[p_newSize]; 
    ASSERT(myArray); 
} 
2

使用new运营商的位置功能,您可以在地方创建对象,避免复制:

位置(3):void *的运营商新的(标准::为size_t大小,无效* ptr)noexcept;

只需返回ptr(不分配存储空间)。 注意,如果函数被new-expression调用,将执行正确的初始化(对于类对象,这包括调用其默认构造函数)。

我建议如下:

A* arrayOfAs = new A[5]; //Allocate a block of memory for 5 objects 
for (int i = 0; i < 5; ++i) 
{ 
    //Do not allocate memory, 
    //initialize an object in memory address provided by the pointer 
    new (&arrayOfAs[i]) A(3); 
}