【问题标题】:C++ copy constructor for class with dynamically allocated array具有动态分配数组的类的 C++ 复制构造函数
【发布时间】:2014-06-15 08:45:41
【问题描述】:

刚开始接触 C++(现在几天),我有 C 背景。

我有一个类,它主要包含一个指向 int 数组的指针,代码如下:

class Array
{
private:
    int * _arr;
    int _size;
public:
    Array();
    Array(int size);
    Array(const Array& obj);  // copy constructor
    ~Array();
    void readInValues();
    void mult(int num);
    void add(int num);
    void printArray();
};

_arr 是一个指向 int 数组的指针,当使用复制构造函数创建一个新实例时,我会在堆上创建一个新的 int 数组(我认为)。在复制构造函数中:

Array::Array( const Array & obj )
{
    _arr = new int[_size];

    for(int i=0;i<_size;i++)
        *_arr[i] = *obj._arr[i];
}

我要做的第一件事是为新数组分配内存(_size 是一种原始类型,因此据我所知会自动复制)。我想做的下一件事是使用循环复制数组本身。这部分编译失败,说非法间接。我不知道为什么......

【问题讨论】:

  • 不,_size 未被复制。你必须自己做。然后,你的循环应该有_arr[i] = obj._arr[i];,但你也可以使用std::copy。您还需要对赋值运算符做一些事情。
  • 当您以 C 为背景使用 C++ 时,您需要做的第一件事完全忘记数组和指针。为了兼容性,C++ 从 C 继承了这些东西。除非您需要与 C 或旧 C++ 代码交互,否则切勿使用它们。在您从词汇表中消除这些概念之前,不要继续讨论其他主题。请改用std::vectorstd::shared_ptrstd::unique_ptr
  • @n.m.我认为这更像是一个练习,因为我怀疑任何人仍然会在 C++ 中实现一个数组,因为它是由标准库提供的。从这个角度来看,很好地理解指针和内存管理实际上非常重要,并且会对您有所帮助。此外,原始指针可以在小规模上表现得更好,并且并非毫无意义,因为它们可以指示所有权中的不同事物:stackoverflow.com/questions/7657718/…
  • 请注意,您还需要一个赋值运算符,可能还需要一个移动构造函数/移动赋值运算符。见三/五法则。

标签: c++ arrays copy-constructor dynamic-allocation


【解决方案1】:

这个:

Array::Array( const Array & obj )
{
    _arr = new int[_size];

    for(int i=0;i<_size;i++)
        *_arr[i] = *obj._arr[i];
}

可能是这样的:

Array::Array( const Array & obj )
    : _arr(obj._size ? new int[obj._size] : nullptr)
    , _size(obj._size)
{
    if (_arr)
        std::copy(obj._arr, obj._arr+_size, _arr);
}

特别注意初始化列表的使用。其他值得一提的:

  1. 不要使用带有前导下划线的标识符。除了使代码阅读起来非常痛苦之外,您还可以快速发现自己使用了由实现保留的标识符。这里不是这种情况,但我敢打赌这不是故意的。

  2. 您还需要一个赋值运算符来实现Rule of Three。值得庆幸的是,一个体面的复制构造函数使这变得微不足道。

  3. 不要这样做任何。这就是为什么,在设计决策过程中痛哭流涕和咬牙切齿的情况下,过去多年的全能 C++ 标准委员会如此选择以std::vector&lt;&gt; 祝福我们。如果你这样做是为了学习,很好。您最终将了解到,一旦您接受标准库及其所有罪恶的乐趣,您需要多么少见。

如果这是一个学习练习,请恭喜并愉快地忽略上面的 (3)。对于 (2),您现在可以使用 copy/swap idiom,因为您有一个合理的 copy-ctor:

Array& Array::operator =(Array obj) // value-param intentional
{
    std::swap(_arr, obj._arr);
    std::swap(_size, obj._size);
    return *this;
}

祝你好运。

【讨论】:

  • 为什么要在赋值运算符中使用交换。我会理解使用移动。您不只是通过首先调用复制构造函数然后使用交换对每个成员分配三次来创建不必要的开销吗?一次到临时变量,一次到你的“*this”,一次到obj,返回后销毁?
  • 交换依赖于临时对象进行复制,而不是在操作符 = 中进行复制。它还依赖于在销毁时临时释放 obj 替换 _arr。除了不太明显之外,这种方法的缺点是可能导致额外的成员变量复制,因为交换本质上是复制两个变量,就像 _size 的情况一样。使用 const Array& 的传统方法不需要做额外的复制。
  • @Pinna_be 您的问题将通过阅读问题得到很好的回答,并在答案中提供答案 (see here)。简而言之,与动态分配相比,交换两个 scalar 成员变量没什么。所写的赋值运算符是异常安全的。传统的const 引用提出了在动态管理发挥作用时可能存在无效状态的问题。阅读链接。它在详细描述方面比我希望在评论中做得更好。
  • @WhozCraig 谢谢你的链接。当复制构造函数和 assignmentinstructor 具有或多或少相同的功能时,这通常可以成为编写更好代码的解决方案。
  • @Pinna_be 很高兴它有帮助。真正的好处是异常管理。很难理解,但值得一读。
【解决方案2】:

_size 没有初始值,因为您在使用它之前没有将它作为参数传递或在其他任何地方初始化。

另外,不要这样做*_arr[i] = *obj._arr[i];

这没有意义,因为arr[i] 本身就是*(arr+i)

做:

_arr[i] = obj._arr[i];

【讨论】:

  • 只有在类声明中有初始值时才会执行原始数据类型的副本?其他构造函数我已经在 _size 中放置了一个值,所以当调用复制构造函数时,_size 中总是有一个值。另一件事 obj._arr[i] 没有返回数组中第 i 个成员的内存地址?
  • 不,_size 不会被复制。此外,您只想复制这些值,所以 _arr[i] = obj._arr[i]; 就可以了。
【解决方案3】:

当你这样做时,你应该没问题

Array::Array( const Array & obj ):
    _size(obj._size) 
{
    _arr = new int[_size];

    for(int i=0;i<_size;i++)
        _arr[i] = obj._arr[i];
}

在第 2 行你得到初始化列表,阅读这个http://www.cprogramming.com/tutorial/initialization-lists-c++.html

关键是,当您编写自定义复制构造函数时,只有您明确表示要复制的内容才会被复制。复制构造函数将首先为所有成员调用默认初始化程序,除非您使用初始化程序值。

另外,也许是对性能的一点提示,您是否查看过 memcpy? http://www.cplusplus.com/reference/cstring/memcpy/ 可能更高效 :D

我注意到的另一件事是您取消引用 *_arr[i],它不是指针而是对值的引用。

编辑有人注意到我的更好实际上并没有更好。我改了。

【讨论】:

  • memcpy(_arr,obj._arr,sizeof(obj._arr)); ?似乎从 obj._arr 复制内存地址,但 memcpy 等待指针,这些是指针。
  • @user34920 我宁愿说 memcpy(_arr, obj._arr, sizeof(_arr[0])*_size);指针的大小与长整数一样长(在大多数机器上),因此您应该将第一个元素的大小乘以您拥有的元素数量。
【解决方案4】:

_size 是原始类型被复制实际上是一个真实的陈述,但你没有指定完整的句子。
当您不声明复制构造函数时,默认实现将是这样的

Array::Array( const Array & obj )
{
    this._size = obj._size;
    this._arr = obj._arr;
}

这是您不想要的,因为复制的对象将共享 _arr 变量。
但是当您实现了复制构造函数时,您需要完全实现它。 在您的情况下,您没有使用_size 的值。因此它将是未定义的。

编译失败是因为*_arr[i] = *obj._arr[i];这行。您正在使用 *.只需使用_arr[i] = obj._arr[i]; 复制即可。

【讨论】:

    【解决方案5】:

    这是我的解决方案。也许会有所帮助。

    Array.h
    
    #pragma once
    class Array {
    
    public:
    
        Array();
        Array(int size);
        Array(const Array& src);
        Array operator=(const Array& rhs);
        ~Array();
    
        void readInValues();
        void mult(int num);
        void add(int num);
        void printArray();
    
    private:
        int *_arr;
        int _size;
    
    };
    

    我在引用三个规则时添加了赋值运算符。它是 Array.cpp

    #include "Array.h"
    
    Array::Array()
        :_size(10) {
    
        _arr = new int[_size];
    }
    
    Array::Array(int size)
        : _size(size) {
    
        _arr = new int[_size];
    }
    
    Array::Array(const Array& src) {
        _size = src._size;
        _arr = new int[_size];
    
        for (int i = 0; i < _size; i++) {
            _arr[i] = src._arr[i];
        }
    }
    
    Array Array::operator=(const Array& rhs) {
        if (this == &rhs) {
            return (*this);
        }
    
        delete[] _arr;
    
        _size = rhs._size;
        _arr = new int[_size];
    
        for (int i = 0; i < _size; i++) {
            _arr[i] = rhs._arr[i];
        }
    }
    
    Array::~Array() {
        delete[] _arr;
    }
    

    【讨论】:

      猜你喜欢
      • 2019-03-18
      • 1970-01-01
      • 2013-03-06
      • 1970-01-01
      • 1970-01-01
      • 2018-11-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多