欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-14 11:45:40
...

原理:

依据: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.

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() 虚函数

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

 

有些时候我们在定义一个类的时候不希望其中的拷贝控制成员(拷贝构造和拷贝赋值)起作用,也就是阻止拷贝,这时候可能有人会想,那我们干脆不定义这样的拷贝控制函数不就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已经证明了 子类默认调用父类的构造方法,如果父类中没有无参的构造函数,就会出现编译错误。

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

但是如果调用了父类的有参构造函数就没有错误了。
【总结】【C++11】禁止拷贝新方法与相关知识点

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

 

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

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

扩展:

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

相关标签: 数据结构 C11