可以安全地使用复制/移动ctors来实现复制/移动赋值运算符吗?

时间:2014-02-14 00:56:33

标签: c++ design-patterns c++11 constructor idioms

我认为以下代码比复制和交换习惯用法更好。

通过这种方式,您可以使用两个宏来封装复制和移动赋值运算符的定义。换句话说,您可以避免在代码中明确定义它们。因此,您只能将注意力集中在ctors和dtor上。

该方法有什么缺点吗?

class A
{
public:
    A() noexcept
        : _buf(new char[128])
    {}

    ~A() noexcept
    {
        if (_buf)
        {
            delete[] _buf;
            _buf = nullptr;
        }
    }

    A(const A& other) noexcept
        : A()
    {
        for (int i = 0; i < 128; ++i)
        {
            _buf[i] = other._buf[i];
        }
    }

    A(A&& other) noexcept
        : _buf(other._buf)
    {
        _buf = nullptr;
    }

    A& operator =(const A& other) noexcept
    {
        if (this != &other)
        {
            this->~A();
            new(this) A(other);
        }

        return *this;
    }

    A& operator =(A&& other) noexcept
    {
        if (this != &other)
        {
            this->~A();
            new(this) A(static_cast<A&&>(other));
        }

        return *this;
    }

private:
    char* _buf;
};

3 个答案:

答案 0 :(得分:2)

class A
{
public:
    A() noexcept
        : _buf(new char[128])
    {}

在上文中,如果A()引发异常,std::terminate()将调用new char[128]

    ~A() noexcept
    {
        if (_buf)
        {
            delete[] _buf;
            _buf = nullptr;
        }
    }

在上面,看起来不错。可简化为:

    ~A() noexcept
    {
        delete[] _buf;
    }



    A(const A& other) noexcept
        : A()
    {
        for (int i = 0; i < 128; ++i)
        {
            _buf[i] = other._buf[i];
        }
    }

在上文中,如果std::terminate()抛出异常,将调用new char[128]。但其他方面都很好。

    A(A&& other) noexcept
        : _buf(other._buf)
    {
        _buf = nullptr;
    }

在上面,看起来不错。

    A& operator =(const A& other) noexcept
    {
        if (this != &other)
        {
            this->~A();
            new(this) A(other);
        }

        return *this;
    }

在上面,通常我会说这是危险的。如果new(this) A(other);抛出怎么办?在这种情况下,它不会,因为如果它尝试,程序将终止。这是否安全行为取决于应用程序(终止对于Ariane 5不起作用,但在更普通的应用程序中工作正常)。

    A& operator =(A&& other) noexcept
    {
        if (this != &other)
        {
            this->~A();
            new(this) A(static_cast<A&&>(other));
        }

        return *this;
    }

以上应该可以正常工作。虽然我不确定它是否优于下面的非分支版本,但性能相当。行为差异在于下面的版本不是自动分配的无操作。然而,我认为自我移动分配不一定是无操作,因为其中一个后置条件表明结果值未指定(其他后置条件表明它被指定,导致不可靠的矛盾)。

    A& operator =(A&& other) noexcept
    {
        delete [] _buf;
        _buf = nullptr;
        _buf = other._buf;
        other._buf = nullptr;
        return *this;
    }

答案 1 :(得分:1)

它将在您提供的上下文中正常工作。

当A是多态类并且具有虚析构函数时,这种技术将是灾难性的。

答案 2 :(得分:1)

您可以 使用unique_ptr<char[]> _buf来简化此课程:

class A
{
public:
    static const std::size_t bufsize = 128;

    A() noexcept
        : _buf(new char[bufsize])
    {}

    A(const A& other) noexcept
        : A()
    {
        copy_from(other);
    }

    A(A&& other) noexcept = default;

    A& operator =(const A& other) noexcept
    {
        copy_from(other);
        return *this;
    }

    A& operator =(A&& other) noexcept = default;

private:
    void copy_from(const A& other) noexcept {
        std::copy_n(other._buf.get(), bufsize, _buf.get());
    }

    std::unique_ptr<char[]> _buf;
};

由于它避免了“聪明的”delete +展示位置new,因此在未来的变化中,该课程更短,更具惯用性,更安全。我个人会从noexceptA()中移除A(const A&),但是如果您希望程序terminate分配失败,那么您可以选择;)

如果你的目标只是避免编写任务操作员 - 而且我不怪你,他们是令人讨厌的平庸 - 你应该设计到Rule of Zero

class A
{
public:
    static const std::size_t bufsize = 128;

    A() : _buf(bufsize) {}

private:
    std::vector<char> _buf;
};

那里 - 所有隐含的副本和移动。