动态分配时,指向免费商店上的新地址是一种好做法吗? 是C++入门第五版的练习:

动态分配时,指向免费商店上的新地址是一种好做法吗? 是C++入门第五版的练习:,c++,pointers,dynamic-memory-allocation,copy-assignment,C++,Pointers,Dynamic Memory Allocation,Copy Assignment,练习13.22:假设我们希望HasPtr表现得像一个值。 也就是说,每个对象都应该有自己的字符串副本 对象指向。我们将显示复制控件的定义 下一节的成员。然而,你已经知道你所知道的一切 需要知道如何实现这些成员。写下HasPtr副本 在阅读之前,构造函数和复制赋值运算符。(第511页) 类的代码HasPtr: class HasPtr { public: //! default constructor HasPtr(const std::string &s = std::s

练习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
指向一个新地址。我想知道这种细微的差别是否有任何意义?在类似情况下,我是否应该始终更改指向新地址的指针?为什么?

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

delete ps;
ps = new std::string(*hp.ps);
在这里,如果执行自赋值,您可能会同时删除源和目标中的ps,从而使其在新语句中的使用错误(尽管它在大多数情况下可能会起到欺骗性的作用)

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


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

您的版本对于自我分配是不安全的

delete ps;
ps = new std::string(*hp.ps);
在这里,如果执行自赋值,您可能会同时删除源和目标中的ps,从而使其在新语句中的使用错误(尽管它在大多数情况下可能会起到欺骗性的作用)

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


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

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

delete ps;
ps = new std::string(*hp.ps);
当第二行代码引发异常时,
ps
将指向过时成员。顺便说一句,如果您最终自行指定了对象,则实际上只有在访问对象之前才删除对象的内存。因此,最好先复制右侧的内容,然后将内容放置到位,最后释放资源

碰巧,这些正是

  • 复制构造函数
  • 一种
    swap()
    操作,通常用于任何类型的资源
  • 析构函数
  • 利用这三种操作的方法称为复制和交换习惯用法:


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

    delete ps;
    ps = new std::string(*hp.ps);
    
    当第二行代码引发异常时,
    ps
    将指向过时成员。顺便说一句,如果您最终自行指定了对象,则实际上只有在访问对象之前才删除对象的内存。因此,最好先复制右侧的内容,然后将内容放置到位,最后释放资源

    碰巧,这些正是

  • 复制构造函数
  • 一种
    swap()
    操作,通常用于任何类型的资源
  • 析构函数
  • 利用这三种操作的方法称为复制和交换习惯用法:


    在功能上,我只能看到一个区别

        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
    仍然指向一个有效字符串,这样您就不会有格式错误的对象


    一个问题的严重程度取决于几个不同的方面,但通常更好的做法是避免出现任何形式错误的对象。

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

        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
    仍然指向一个有效字符串,这样您就不会有格式错误的对象


    问题的严重程度取决于几个不同的方面,但通常更好的做法是避免出现格式错误的对象。

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

    • 自我分配
    • 例外安全
    通常,您希望您的成员函数提供强大的异常保证,即当赋值失败时(在您的情况下,它可以在
    运算符new
    字符串的复制构造函数中),程序状态不会更改

    我认为现代的做法是提供
    s