【总结】【C++11】禁止拷贝新方法与相关知识点

2023-11-03

原理:

依据:https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-copy-virtual

C.67: A polymorphic class should suppress copying

Reason

A polymorphic class is a class that defines or inherits at least one virtual function. It is likely that it will be used as a base class for other derived classes with polymorphic behavior. If it is accidentally passed by value, with the implicitly generated copy constructor and assignment, we risk slicing: only the base portion of a derived object will be copied, and the polymorphic behavior will be corrupted.(因为一个普通的拷贝操作只会拷贝基类成员。

bed例:拷贝指向子类的基类后,只会表现基类的行为

#include <iostream>
using namespace std;

class B { // GOOD: polymorphic class suppresses copying
public:
   // B(const B&) = delete;
  //  B& operator=(const B&) = delete;
    virtual char m() { return 'B'; }
    // ...
};

class D : public B {
public:
    char m() override { return 'D'; }
    // ...
};

void f(B& b) {
    auto b2 = b; // oops, slices the object; b2.m() will return 'B'
    cout<< b.m()<<endl;  //here return 'D'
    cout<< b2.m()<<endl;  //here return 'B'
}
int main()
{
D d;
f(d);
}

more good:基类设为delete,如果copy则会报错

#include <iostream>
using namespace std;

class B { // GOOD: polymorphic class suppresses copying
public:
    B(const B&) = delete;
    B& operator=(const B&) = delete;
    virtual char m() { return 'B'; }
    // ...
};

class D : public B {
public:
    char m() override { return 'D'; }
    // ...
};

void f(B& b) {
    auto b2 = b;  // ok, compiler will detect inadvertent copying, and protest
    cout<< b2.m()<<endl;
}
int main()
{
D d;
f(d);
}

(扩展:如果子类有自己的构造函数,则不继承基类的特性。可以拷贝子类)

#include <iostream>
using namespace std;

class B { // GOOD: polymorphic class suppresses copying
public:
    B()=default;
    virtual ~B()=default;
    B(const B&) = delete;
    B& operator=(const B&) = delete;
    virtual char m() { return 'B'; }
    // ...
};

class D : public B {
public:
    D()=default;
    virtual ~D()=default;
    D(const D& d){cout<<"copy"<<endl;}
    char m() override { return 'D'; }
    // ...
};

void f(D& d) {  //here is derived
    auto d2 = d;
    cout<< d.m()<<d2.m()<<endl;
}
int main()
{
D d;
f(d);
}

Note

If you need to create deep copies of polymorphic objects, use clone() functions: see C.130.

Exception

Classes that represent exception objects need both to be polymorphic and copy-constructible.

Enforcement

  • Flag a polymorphic class with a non-deleted copy operation.
  • Flag an assignment of polymorphic class objects.

C.130: For making deep copies of polymorphic classes prefer a virtual clone function instead of copy construction/assignment

Reason

Copying a polymorphic class is discouraged due to the slicing problem, see C.67. If you really need copy semantics, copy deeply: Provide a virtual clone function that will copy the actual most-derived type and return an owning pointer to the new object, and then in derived classes return the derived type (use a covariant return type).

 

基类应该禁止拷贝,但是提供一个 clone() 虚函数(?禁止拷贝的同时,实现clone没成功)

这是为了防止对象被“截断“。因为一个普通的拷贝操作只会拷贝基类成员。对于一个有虚函数的类(会被继承),不应该有拷贝构造函数和拷贝赋值函数。

 

有些时候我们在定义一个类的时候不希望其中的拷贝控制成员(拷贝构造和拷贝赋值)起作用,也就是阻止拷贝,这时候可能有人会想,那我们干脆不定义这样的拷贝控制函数不就OK了,但悲催的是如果自己不定义,好心的编译器也会及时的学习雷锋好榜样帮你合成定义,即合成拷贝构造、合成拷贝赋值。既然如此,应该如何操作实现我们的目的呢,结合effective C++的条款中给出两种方式,而C++11当中也给出了一种,下面由一个例子说起,简单介绍三种阻止拷贝的方式。 
    有一个房产销售系统类,用来描述待售房屋:

class HomeForSale{...};

每一位房屋销售人员都会号称每一笔房屋资产都是天上地下独一无二,没有两笔完全相像。所以我们也认为,为这个类对象做一份副本有点没道理——怎么能够复制某些先天独一无二的东西呢?所以,HomeForSale对象的拷贝动作以失败收场:

 

HomeForSale h1;
HomeForSale h2;
HomeForSale h3(h1);  //此时调用拷贝构造函数
h1=h2;           //此时调用拷贝赋值运算符

按照我们所想,代码中的拷贝构造及赋值操作都不应该通过编译,也就是应该阻止这样的拷贝操作,前面已经提到过即使程序员自己不定义拷贝控制成员,编译器也会自行生成默认版本。那么应该怎样做呢。 
(1)将拷贝控制成员函数声明为private且不进行定义 
这种方式,将拷贝构造函数和拷贝赋值运算符声明为private,阻止了编译器创建合成版本,同时这些private函数也阻止了用户调用。而不对其进行定义,也做到了即使是成员函数和友元也无法进行调用。

 

//将HomeForSale的拷贝构造和拷贝赋值声明为private且不对其定义
class HomeForSale{
public:
    ...
private:
    ...
    HomeForSale(const HomeForSale&);    //只有声明
    HomeForSale& operator=(const HomeForSale&);
};

(2)设计一个专门为了阻止拷贝动作的基类,将连接期错误转移至编译器,子类在继承时,子类的拷贝构造函数和拷贝复制运算符也会被禁用。(因为子类得拷贝和构造需要依托父类?)

 

class Uncopyable{
protected:
    Uncopyable() {}        //Uncopyable() = default;  相同
    ~Uncopyable() {}      //~Uncopyable() = default;  相同
private:
    Uncopyable(const Uncopyable&);    //Uncopyable(const Uncopyable&) = delete;双重。。
    Uncopyable& operator=(const Uncopyable);   //    Uncopyable& operator=(const Uncopyable) = delete;
};

为阻止HomeForSale对象被拷贝,唯一需要做的就是继承Uncopyable:

 

class HomeForSale:public Uncopyable{
        ...
};

这种方式行得通,当尝试拷贝HomeForSale对象,编译器就试着生成一个拷贝构造函数和一个拷贝赋值运算符,而这些函数的编译器合成版会尝试调用其基类的对应成员,那些调用会被编译器拒绝,因为其基类的拷贝函数是private。 

 

扩展:

1.派生类继承基类时,若派生类没有定义自己的拷贝构造函数和拷贝复制运算符,则基类的拷贝属性会传递到派生类。(上述情况)


#include <iostream>

 

class Basic

{

public:

    Basic() = default;

    ~Basic() = default;

 

protected:

    Basic(const Basic& ) = delete;

    Basic& operator=(const Basic& ) = delete;

};

 

class Derived : public Basic

{

public:

    Derived() = default;

    Derived(int m) : value(m) {}

 

private:

    int value;

 

};

 

int main() {

    Derived d1;

    Derived d2(2);

 

    Derived d3(d1); //该语句clion编译时会报错

    return 0;

}

2.派生类继承基类时,若派生类已定义自己的拷贝构造函数和拷贝复制运算符,则基类的拷贝属性不会传递到派生类


#include <iostream>

 

class Basic

{

public:

    Basic() = default;

    ~Basic() = default;

 

protected:

    Basic(const Basic& ) = delete;

    Basic& operator=(const Basic& ) = delete;

};

 

class Derived : public Basic

{

public:

    Derived() = default;

    Derived(int m) : value(m) {}

 

    Derived(const Derived& derived) : value(derived.value) {}   //派生类的拷贝构造函数

    Derived& operator=(const Derived& derived)                  //派生类的拷贝复制运算符

    {

        value = derived.value;

    }

 

public:

    int value;

 

};

 

int main() {

    Derived d1;

    Derived d2(2);

 

    Derived d3(d2); //该语句顺利执行

 

    std::cout << d3.value << std::endl;

    return 0;

}

3.子类会默认调用父类的无参构造方法,则当父类中没有无参构造函数时,子类必须调用父类有参的构造函数,因为1已经证明了 子类默认调用父类的构造方法,如果父类中没有无参的构造函数,就会出现编译错误。

但是如果调用了父类的有参构造函数就没有错误了。

(3)将拷贝控制函数定义为“删除的” (C11新特性)
在C++11中,通过在函数的参数列表后面加上”=delete”来指出我么希望将它定义为删除的,即虽然声明了它们,但不能以任何方式使用。

 

class HomeForSale{
public:
    HomeForSale(const HomeForSale&)=delete;    //定义为删除的
    HomeForSale& operator=(const HomeForSale&)=delete;
};

析构函数不能是delete的,如果析构函数被删除,就无法销毁此类型的对象了。

扩展:

我们可以对任何函数指定=delete。可用于重载禁用或模板禁用

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【总结】【C++11】禁止拷贝新方法与相关知识点 的相关文章

  • DRBD分布式存储解决方案实战

    一 DRBD分布式存储解决方案 1 DRBD简介 DRBD的全称为 Distributed Replicated Block Device DRBD 分布式块设备复制 DRBD是由内核模块和相关脚本而构成 用以构建高可用性 HA 的集群 其

随机推荐