动态分配时,指向免费商店的新地址是一种好习惯吗?

时间:2014-01-01 01:50:30

标签: c++ pointers dynamic-memory-allocation copy-assignment

以下是 C ++ Primer 5th Edition 的练习:

  

练习13.22:假设我们希望HasPtr表现得像一个值。   也就是说,每个对象都应该有自己的字符串副本   对象指向。我们将展示复制控件的定义   成员在下一节。但是,你已经了解了你的一切   需要知道实施这些成员。编写HasPtr副本   读取之前的构造函数和复制赋值运算符。(页511)

班级HasPtr的代码:

class HasPtr
{
public:
    //! default constructor
    HasPtr(const std::string &s = std::string()):
        ps(new std::string(s)), i(0) { }

    //! copy constructor
    HasPtr(const HasPtr& hp) : ps(new std::string(*hp.ps)), i(hp.i) { }

    HasPtr&
    operator = (const HasPtr& hp);

    ~HasPtr()
    {
        delete ps;
    }

private:
    std::string *ps;
    int    i;
};

此复制赋值运算符的代码:

HasPtr&
HasPtr::operator = (const HasPtr &hp)
{
    delete ps;
    ps = new std::string(*hp.ps);

    i  = hp.i;

    return *this;
}

本书以下部分提供的代码:

HasPtr& 
HasPtr::operator = (const HasPtr &rhs)
{
    auto newp = new string(*rhs.ps);   // copy the underlying string
    delete ps;       // free the old memory
    ps = newp;       // copy data from rhs into this object
    i = rhs.i;
    return *this;    // return this object
}

通过逐步执行,我发现两个代码之间略有不同。在我的代码中,它不会更改ps指向的地址,而书中的代码会使ps指向新地址。我想知道这种微妙的差异是否有任何重大意义?在类似情况下,我是否应始终将指针更改为新地址?为什么呢?

4 个答案:

答案 0 :(得分:2)

您的版本对于自行分配不安全。

delete ps;
ps = new std::string(*hp.ps);

这里,如果进行自我赋值,你可能会删除源和目标中的ps,使其在新语句中的使用错误(在大多数情况下它可能看似起作用)。

您可以直接将new的值分配到您的成员变量中,但在知道是否需要之前,您不能随意删除ps

如果您在执行代码之前测试了自我分配,例如if (this!=&hp),那么它会更好,但仍然不理想(请参阅其他地方的异常安全评论)。

答案 1 :(得分:2)

您的代码在自我分配和异常方面存在问题:假设内存分配引发std::bad_alloc异常。在编码时,你应该总是假设内存分配可以出错,尽管实际上很少。在代码中

delete ps;
ps = new std::string(*hp.ps);
当第二行代码抛出异常时,

ps将指向陈旧成员。顺便说一句,如果你最终自我分配对象,你实际上只有delete才能访问它之前的内存。因此,首先复制右侧的内容,然后将事情放到适当位置,最后释放资源是个好主意。

碰巧,这些正是

的操作
  1. 复制构造函数
  2. 对于任何持有资源的类型,您通常需要的swap()操作
  3. 析构函数
  4. 利用这三种操作的方式称为复制和交换习语:

    T& T::operator=(T other) {
        this->swap(other);
        return *this;
    }
    

答案 2 :(得分:1)

从功能上讲,我只能看到一个区别。

    delete ps;
    ps = new std::string(*hp.ps);

如果内存不足,调用new std::string可能会抛出异常。在您的情况下,ps仍然具有旧删除字符串的地址 - 因此格式错误。如果你从异常中恢复,有人可能会取消引用ps并且会发生不好的事情。

    auto newp = new string(*rhs.ps);   // copy the underlying string
    delete ps;       // free the old memory
    ps = newp;       // copy data from rhs into this object

在教科书代码中,在分配新字符串之前,不会删除ps。例外情况下,ps仍指向有效字符串,因此您没有格式错误的对象。

问题有多少取决于一些不同的事情,但通常更好的做法是避免任何形容错误的对象。

答案 3 :(得分:1)

您的代码实际上有两个问题:

  • 自分配
  • 例外安全

您通常希望您的成员函数提供强大的异常保证,即,当赋值失败时(在您的情况下,它可以在operator newstring的复制构造函数中),程序状态不会改变。

我认为现代实践是提供swap函数并使赋值调用复制构造函数。类似的东西:

void HasPtr::swap(HasPtr& rhs)
{
    std::swap(this.ps, rhs.ps);
    std::swap(this.i, rhs.i);
}

HasPtr(const HasPtr& rhs)
{
    ps = new string(*rhs.ps);
    i = rhs.i;
}

HasPtr& operator=(const HasPtr& rhs)
{
    HasPtr temp(rhs);
    this.swap(temp);
    return *this;
}