代码之家  ›  专栏  ›  技术社区  ›  MPelletier

C++中复制构造函数和=运算符重载:可能的公共函数吗?

  •  77
  • MPelletier  · 技术社区  · 16 年前

    因为是复制构造函数

    MyClass(const MyClass&);
    

    且an=操作员过载

    MyClass& operator = (const MyClass&);
    

    具有几乎相同的代码、相同的参数,并且返回时只有不同的参数,是否可以有一个共同的函数供两者使用?

    3 回复  |  直到 8 年前
        1
  •  105
  •   Juan CB Bailey    8 年前

    对。共有两种选择。一种通常不鼓励的做法是 operator= 从复制构造函数显式:

    MyClass(const MyClass& other)
    {
        operator=(other);
    }
    

    但是,提供 运算符= 是一个挑战,当涉及到处理旧的状态和问题产生的自我分配。此外,所有成员和基都会首先初始化默认值,即使它们将从分配给 other . 这甚至可能对所有成员和基都无效,甚至在它有效的情况下,它在语义上是多余的,并且可能实际上是昂贵的。

    一个日益流行的解决方案是 运算符= 使用复制构造函数和交换方法。

    MyClass& operator=(const MyClass& other)
    {
        MyClass tmp(other);
        swap(tmp);
        return *this;
    }
    

    甚至:

    MyClass& operator=(MyClass other)
    {
        swap(other);
        return *this;
    }
    

    swap 函数通常很容易编写,因为它只是交换内部的所有权,不需要清理现有状态或分配新资源。

    复制和交换习惯用法的优点是,它是自动自分配安全的,并且——只要交换操作是不抛出的——也是非常安全的异常。

    为确保异常安全,“手写”的分配运算符通常必须在取消分配受让人的旧资源之前分配新资源的副本,以便在分配新资源时发生异常时,仍然可以返回旧状态。所有这些在复制和交换中都是免费的,但通常更复杂,因此容易出错,从头开始。

    要小心的一件事是确保swap方法是真正的swap,而不是默认值 std::swap 它使用复制构造函数和赋值运算符本身。

    通常是会员制的 掉期 使用。 STD::掉期 对所有基本类型和指针类型都有效且“不引发”。大多数智能指针也可以交换为不抛出保证。

        2
  •  12
  •   sbi    16 年前

    复制构造函数对以前是原始内存的对象执行首次初始化。赋值运算符Otoh用新值覆盖现有值。通常情况下,这涉及到废弃旧资源(例如内存)和分配新资源。

    如果两者之间存在相似性,则表示赋值运算符执行销毁和复制构造。一些开发人员过去常常通过就地销毁和放置副本构造来实际实现分配。然而,这是一个 非常 坏主意。(如果这是在派生类赋值期间调用的基类的赋值运算符,该怎么办?)

    现在人们通常认为的标准成语是什么 swap 查尔斯建议:

    MyClass& operator=(MyClass other)
    {
        swap(other);
        return *this;
    }
    

    这使用复制构造(注意 other 被复制)和销毁(在函数末尾被销毁)--并且它也按正确的顺序使用它们:在销毁之前构造(可能失败)(一定不能失败)。

        3
  •  -2
  •   Matthew    16 年前

    我有点担心:

    MyClass& operator=(const MyClass& other)
    {
        MyClass tmp(other);
        swap(tmp);
        return *this;
    }
    

    首先,当我的大脑在思考“复制”时,阅读“交换”一词会刺激我的常识。另外,我对这个花式把戏的目的表示怀疑。是的,在构建新的(复制的)资源时,任何异常都应该发生在交换之前,这似乎是一种安全的方法,可以确保在使所有新数据生效之前都已填充。

    那很好。那么,在交换之后会发生什么异常呢?(当临时对象超出范围时,旧资源被破坏)从分配的用户的角度来看,操作失败了,但没有失败。它有一个巨大的副作用:复制确实发生了。只是一些资源清理失败了。目标对象的状态已更改,即使操作似乎从外部失败。

    因此,我建议不要用“交换”来进行更自然的“转移”:

    MyClass& operator=(const MyClass& other)
    {
        MyClass tmp(other);
        transfer(tmp);
        return *this;
    }
    

    还有临时对象的构造,但下一步立即行动是在将源的资源移到目标之前释放目标的所有当前资源(并使其无效,这样它们就不会被双重释放)。

    我建议不要构造、移动、销毁,而是构造、销毁、移动。这是最危险的行动,是在其他一切都解决之后最后采取的行动。

    是的,破坏失败是两种方案中的一个问题。数据要么已损坏(在您认为不存在时复制),要么已丢失(在您认为不存在时释放)。失败者胜于败者。没有比坏数据更好的数据。

    转移而不是交换。这是我的建议。

    推荐文章