为什么复制构造函数不需要检查输入对象是否指向自身?

Why does copy constructor not need to check whether the input object pointers to itself or not?

提问人:sunshilong369 提问时间:5/30/2020 最后编辑:user207421sunshilong369 更新时间:5/30/2020 访问量:110

问:

如下面的代码所示,复制赋值运算符必须检查输入对象是否指向自身。我想知道为什么复制构造函数不需要做同样的检查。

我是C++新手。如果能在这个问题上得到一些帮助,我将不胜感激。

  class rule_of_three
    {
        char* cstring; // raw pointer used as a handle to a dynamically-allocated memory block

        void init(const char* s)
        {
            std::size_t n = std::strlen(s) + 1;
            cstring = new char[n];
            std::memcpy(cstring, s, n); // populate
        }
     public:
        rule_of_three(const char* s = "") { init(s); }

        ~rule_of_three()
        {
            delete[] cstring;  // deallocate
        }

        rule_of_three(const rule_of_three& other) // copy constructor
        { 
            init(other.cstring);
        }

        rule_of_three& operator=(const rule_of_three& other) // copy assignment
        {
            if(this != &other) {
                delete[] cstring;  // deallocate
                init(other.cstring);
            }
            return *this;
        }
    };
C++ C++11 复制构造函数 赋值运算符

评论

1赞 Jean-Baptiste Yunès 5/30/2020
因为当你通过复制进行构造时,构造的对象不能是被复制的对象......您可以进行测试,但这种情况永远不会发生。
0赞 HolyBlackCat 5/30/2020
@IgorR。但是存储对未初始化对象的引用是允许的,不是吗?我认为是的。
0赞 user207421 5/30/2020
@HolyBlackCat 如何获取对未初始化对象的引用?
2赞 HolyBlackCat 5/30/2020
@user207421 简单,将其传递给自己的复制者。:P .MyClass foo(foo);
0赞 HolyBlackCat 5/30/2020
@IgorR。我们可以称之为“未初始化的存储”,但这一点仍然存在。

答:

3赞 HolyBlackCat 5/30/2020 #1

有时会发生自我分配,这是正常使用类的一部分。

将尚未构造的对象作为参数传递给其自己的复制(或移动)构造函数是不正常的。虽然本身不是未定义的行为1,但没有充分的理由这样做,而且通常不会发生。它可能是偶然发生的,或者如果有人故意试图破坏你的班级。

因此,传统上复制(和移动)构造函数不会检查 .&other != this

但是,如果您想要一些额外的安全性,没有什么能阻止您这样做:

rule_of_three(const rule_of_three& other) // copy constructor
{ 
    assert(&other != this);
    init(other.cstring);
}

1 [basic.life]/7 似乎允许这样做,只要您不访问尚未构造的对象本身。允许使用它的地址。&

0赞 Luigi 5/30/2020 #2

假设你有像 Stormtroopers 这样的简单对象:

class Stormtrooper
    {
        char* cstring; // raw pointer used as a handle to a dynamically-allocated memory block

        void clone(const char* s)
        {
            std::size_t n = std::strlen(s) + 1;
            cstring = new char[n];
            std::memcpy(cstring, s, n); // populate
        }
     public:
        Stormtrooper(const char* s = "I am a Stormtrooper clone") { clone(s); }

        ~Stormtrooper()
        {
            delete[] cstring;  // deallocate
        }

        Stormtrooper(const Stormtrooper& other) // copy constructor
        { 
            clone(other.cstring);
        }

        Stormtrooper& operator=(const Stormtrooper& other) // copy assignment
        {
            if(this != &other) {
                delete[] cstring;  // deallocate
                clone(other.cstring);
            }
            return *this;
        }
    };

如果您想将 une Stormtrooper 分配给另一个冲锋队,检查两个冲锋队是否已经相同(通常是)很有用。通过这种方式,您可以避免 clone() 操作,因为冲锋队已经相同。

但是,如果你想创建一个新的冲锋队,并且你希望他与另一个冲锋队员相同(像往常一样),你可以复制构造它,在这种情况下,clone() 操作将正确执行。

通过这种方式,您可以非常轻松地创建一整支冲锋队。