写这样的作业有什么问题?

时间:2014-02-14 07:47:43

标签: c++ constructor destructor assignment-operator

前几天我和我的一个朋友就对象分配和构建进行了对话,他指出对象的赋值a = b(语义上)等同于销毁a然后重新进行 - 从b(在同一个地方)构建它。

但是,当然,没有人(我认为)会像这样编写赋值运算符:

class A {
    A& operator=(const A& rhs) {
        this->~A();
        this->A(rhs);
        return *this;
    }

    A& operator=(A&& rhs) {
        this->~A();
        this->A(std::move(rhs));
        return *this;
    }

    // etc.
};

[注意:我不知道如何在现有对象上手动调用构造函数/析构函数(我从来没有这样做过!),因此他们的调用可能没有正式意义,但我想你可以看到这个想法。] < / p>

这种方法有什么问题?我想必须有一个主要的表演限制,但是名单越大越好。

3 个答案:

答案 0 :(得分:2)

这里有一个误用的建筑:

class A {
    A& operator=(const A& rhs) {
        if(&a==this) return *this; 
        this->~A();
        new(this) A(rhs);
        return *this;
    }

    A& operator=(A&& rhs) {
        if(&a==this) return *this; 
        this->~A();
        new(this) A(std::move(rhs));
        return *this;
    }

    // etc.
};

这对于inplace ctor / dtor语义是正确的,而且{是std::allocator在缓冲区中销毁和构造元素的作用,以便必须正确,对吗? / p>

嗯......不正确:这一切都是关于A实际上包含的内容以及A ctor实际上做了什么。

如果A只包含基本类型并且没有自己的资源,那么它就可以了。这不是惯用的,而是正确的。

如果A包含其他一些资源,则需要获取,管理和发布......您可能遇到麻烦。你也是如果A是多态的(如果~A是虚拟的,你会破坏整个对象,但是你只重构A子对象)。

问题是获取资源的构造函数可能会失败,并且不能销毁构造和抛出失败的对象,因为它从未“构造”过。

但是如果你是“分配”,你就不是“创造”,如果就地ctor失败,你的对象将存在(因为它预先存在于自己的范围内),但是处于不能的状态通过进一步的破坏来管理:想想

{
  A a,b;
  a = b;
}

} b,a将被销毁,但如果A(const A&amp;)在a = b中失败,而throwA::A中生成,{{1} }不存在,但会a }将立即跳转到不正确的地方。

更惯用的方法是

throw

现在,

class A
{
   void swap(A& s) noexcept
   { /* exchanging resources between existing objects should never fail: you just swap pointers */ }
public:
   A() noexcept { /* creates an object in a "null" recognizable state */ }
   A(const A& s) { /* creates a copy: may fail! */ }
   A(A&& s) noexcept { /*make it as null and... */ swap(s); } // if `s` is temporary will caryy old resource deletionon, and we keep it's own resource going
   A& operator=(A s) noexcept { swap(s); return *this; }
   ~A() { /* handle resource deletion, if any */ }
};

将在 a=b 中创建b 副本作为s参数(通过operator=)。 如果失败,A::A(const A&)将不存在,sa仍然有效(具有自己的旧值),因此退出的范围将正常销毁。 如果副本成功,则将交换已复制的资源和实际的b,并且当as处死时,将释放旧资源。

通过converse

}

a = std::move(b) 作为临时,b参数通过A(A&amp;&amp;)构造,因此b将与s交换(并变为空),而s将与a交换。最后,s会破坏旧的s资源,a会收到旧的a,而b将处于空状态(因此当它的范围可能会平静下来结束)

必须在bA()中实施“将A设为空”的问题。 这可以通过帮助程序成员(A(A&&),就像init)或指定成员初始值设定项,或通过定义成员的默认初始化值(一次性)

答案 1 :(得分:1)

首先,只有在使用重载operator new()构造对象时才需要手动调用析构函数,并使用std::nothrow重载等一些注意事项。

您需要了解的是复制构造和赋值运算符之间的区别:当从现有对象创建新对象时,将调用复制构造函数,作为现有对象的副本。当已经初始化的对象从另一个现有对象分配新值时,将调用赋值运算符。

总而言之,您提供的赋值运算符的示例没有意义 - 它必须具有不同的语义。

如果您还有其他问题,请发表评论。

答案 2 :(得分:0)

首先直接调用复制构造函数是不合法的(至少在符合C ++的编译器中...... VS2012允许),因此不允许以下内容:

  // assignment operator
  A& operator=(const A& rhs) {
  this->~A();
  this->A::A(rhs); <---  Invalid use

此时您可以依赖编译器优化(请参阅copy elision和RVO)或在堆上分配它。

如果您尝试执行上述操作,可能会出现许多问题:

1)您可能在复制构造函数的表达式中抛出异常

在这种情况下,您将拥有

  // assignment operator
  A& operator=(const A& rhs) {
    cout << "copy assignment called" << endl;
    this->~A();
    A newObj(rhs); // Can throw and A is in invalid state!
    return newObj;
  }

为了确保安全,您应该使用复制和交换习语:

set& set::operator=(set const& source)
{
    /* You actually don't need this. But if creating a copy is expensive then feel free */
    if (&source == this)
        return;

    /*
     * This line is invoking the copy constructor.
     * You are copying 'source' into a temporary object not the current one.
     * But the use of the swap() immediately after the copy makes it logically
     * equivalent.
     */
    set tmp(source);
    this->swap(tmp);

    return *this;
}

void swap(set& dst) throw ()
{
    // swap member of this with members of dst
}

2)您可能遇到动态分配内存的问题

如果A的两个实例共享一个指针,你可能有一个悬空指针才能释放它

  a = a; // easiest case

  ...

  // assignment operator
  A& operator=(const A& rhs) {
  this->~A(); <-- Freeing dynamically allocated memory
  this->A::A(rhs); <---  Getting a pointer to nowhere
3)正如Emilio所说,如果该类是多态的,那么你将无法重新实例化该子类(除非你以某种方式使用类似CRTP的技术来欺骗它)

4)最后,分配和复制构造是两种不同的操作。如果A包含重新获取昂贵的资源,您可能会遇到很多麻烦。