zoukankan      html  css  js  c++  java
  • 对待拷贝构造函数和赋值函数的3种境界

      对待拷贝构造函数和赋值函数有3种境界:不写;禁用;正确编写。

      1) 不写。不写代码是编程的最高境界。有一种说法:最完美的代码是无以复减的代码。不写的代码永远不会出错,也不需要维护。任何存在的代码都需要维护,只要代码可能变化。

      C++的编译器可以提供默认的构造函数、析构函数、拷贝构造函数、赋值函数、一对取址运算符(*, &)。C++的默认构造函数仅对内置变量随机赋值,具有不确定性,一般不建议使用默认构造函数;对于析构函数讨论比较多,这里不加讨论。但有一个准则:如果需要编写任一特定的析构函数、拷贝构造函数或者赋值函数,那么你也需要编写其余的两个。

      何时不写呢?只要类(包括类中的类变量)不拥有任何资源,那么就可利用编译器的默认版本。常见资源有:内存、文件和端口。任何资源都需要显式的获取和释放,C++的默认拷贝和赋值操作并无法保证正确的资源获取和释放。

      如果直接使用编译器版本,最好加注释,让代码阅读者更清楚。

      2)禁用。禁用代码将给代码的使用者明确的提示,防止误用。如果你定义的类,并不需要拷贝和赋值,请明确禁用,而不是让使用者误用编译器提供的错误版本。示例:

    class T {// …

    private:                        // make T non-copyable

      T( const T& );                // not implemented

      T& operator=( constT& );     // not implemented

    };

      3)巧妙正确地写。只要类拥有自己的资源,用户都需要自行编写拷贝构造函数和赋值函数,或者直接禁止(直接使用对象指针或引用,这样的类不具有值属性)。代码示例:

    代码
    #include <iostream>
    #include
    <string>

    using namespace std;

    template
    <class T>
    class NamedPtr
    {
    public:
    explicitNamedPtr(
    const std::string& name);
    //NamedPtr(conststd::string& name, T *data); //note:user manages the memory
    ~NamedPtr();

    NamedPtr(constNamedPtr
    & rhs);
    NamedPtr
    &operator=(const NamedPtr& rhs);

    voidPrint()
    const;
    private:
    std::stringm_name;
    T
    *m_data; // can be T or T[]. Atthis example use T only
    };

    template
    <class T>
    NamedPtr
    <T>::NamedPtr(conststd::string& name)
    : m_name(name)
    , m_data(NULL)
    {
    m_data
    = new T(); // T must have a defaultconstructor
    }

    //template<class T>
    //NamedPtr<T>::NamedPtr(conststd::string& name, T *data)
    //: m_name(name)
    //, m_data(data)
    //{
    //}

    template
    <class T>
    NamedPtr
    <T>::~NamedPtr()
    {
    deletem_data;
    m_data
    = NULL;
    }

    template
    <class T> //Note: copyconstructor won't call other constructors, it will create a new object byitself
    NamedPtr<T>::NamedPtr(constNamedPtr<T>& rhs)
    {
    m_data
    = new T(); // T must have a defaultconstructor
    *this = rhs; //call assign function to avoid duplicated code
    }

    template
    <class T>
    NamedPtr
    <T>&NamedPtr<T>::operator=(const NamedPtr<T>& rhs)
    {
    //1.check assign self.
    if(this == &rhs) // check by pointer. Note: : 1) fast; 2) avoid to destroyself resource and crash!
    return*this;

    //2.assign to all data members
    m_name= rhs.m_name;
    *m_data= *rhs.m_data; // just copy the value
    // Note:sometime you need to destroy the resource first,
    // then create a new resource, such as copying a string,
    // re-opena file or socket

    //3.return a reference
    return*this;
    }

    template
    <class T>
    void NamedPtr<T>::Print() const
    {
    cout
    <<"name: "<<m_name<<endl;
    }

    //////////////////////////////////////
    int main()
    {
    NamedPtr
    <int>a("test a");
    NamedPtr
    <int>b("test b");
    NamedPtr
    <int>c(a);

    a.Print();
    b.Print();
    c.Print();

    c
    = b;
    c.Print();
    return0;
    }

     =============================================

    [附录] C++拷贝构造函数的几个细节(转载)

    拷贝构造函数是C++最基础的概念之一,大家自认为对拷贝构造函数了解么?请大家先回答一下三个问题:

    1. 以下函数哪个是拷贝构造函数, 为什么?

    1. X::X(const X&);  

    2. X::X(X);   

    3. X::X(X&, int a=1);  

    4. X::X(X&, int a=1, b=2);  

    2. 一个类中可以存在多于一个的拷贝构造函数吗?

    3. 写出以下程序段的输出结果, 并说明为什么? 如果你都能回答无误的话,那么你已经对拷贝构造函数有了相当的了解。

    1. #include    
    2. #include    
    3.   
    4. struct X {   
    5.   template<typename T>   
    6.   X( T& ) { std::cout << "This is ctor." << std::endl; }   
    7.   
    8.   template<typename T>   
    9.     X& operator=( T& ) { std::cout << "This is ctor." << std::endl; }   
    10. };   
    11.   
    12. void main() {   
    13.   X a(5);   
    14.   X b(10.5);   
    15.   X c = a;   
    16.   c = b;   
    17. }  

    解答如下:

    1. 对于一个类X, 1) 如果一个构造函数的第一个参数是下列之一:

    a) X&

    b) const X&

    c) volatile X&

    d) const volatile X&

    2) 且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数. 

    1. X::X(const X&);  //是拷贝构造函数   

    2. X::X(X&, int=1); //是拷贝构造函数  

    2. 类中可以存在超过一个拷贝构造函数, 

    1. class X {      
    2. public:      
    3.   X(const X&);      
    4.   X(X&);            // OK   
    5. };  

    注意,如果一个类中只存在一个参数为X&的拷贝构造函数, 那么就不能使用const X或volatile X的对象实行拷贝初始化.

    1. class X {   
    2. public:   
    3.   X();   
    4.   X(X&);   
    5. };   
    6.     
    7. const X cx;   
    8. X x ( cx);    //compiler error   

      如果一个类中没有定义拷贝构造函数, 那么编译器会自动产生一个默认的拷贝构造函数.这个默认的参数可能为X::X(const X&)或X::X(X&), 由编译器根据上下文决定选择哪一个.

      默认拷贝构造函数的行为如下:

      默认的拷贝构造函数执行的顺序与其他用户定义的构造函数相同,执行先父类后子类的构造. 拷贝构造函数对类中每一个数据成员执行成员拷贝(memberwiseCopy)的动作.

    a) 如果数据成员为某一个类的实例, 那么调用此类的拷贝构造函数.

    b) 如果数据成员是一个数组, 对数组的每一个执行按位拷贝.

    c) 如果数据成员是一个数量, 如int, double, 那么调用系统内建的赋值运算符对其进行赋值.

    3.  拷贝构造函数不能由成员函数模版生成. 

    1. struct X {   
    2.     template<typename T>   
    3.     X( const T& );    // NOT copy constructor, T can't be X   
    4.   
    5.     template<typename T>   
    6.     operator=( const T& );  // NOT copy assign operator, T can't be X   
    7. };   
    8.   

      原因很简单, 成员函数模版并不改变语言的规则, 而语言的规则说, 如果程序需要一个拷贝构造函数而你没有声明它, 那么编译器会为你自动生成一个. 所以成员函数模版并不会阻止编译器生成拷贝构造函数, 赋值运算符重载也遵循同样的规则.(参见EffectiveC++ 3edition, Item45)

  • 相关阅读:
    ubuntu安装jdk的两种方法
    LeetCode 606. Construct String from Binary Tree (建立一个二叉树的string)
    LeetCode 617. Merge Two Binary Tree (合并两个二叉树)
    LeetCode 476. Number Complement (数的补数)
    LeetCode 575. Distribute Candies (发糖果)
    LeetCode 461. Hamming Distance (汉明距离)
    LeetCode 405. Convert a Number to Hexadecimal (把一个数转化为16进制)
    LeetCode 594. Longest Harmonious Subsequence (最长的协调子序列)
    LeetCode 371. Sum of Two Integers (两数之和)
    LeetCode 342. Power of Four (4的次方)
  • 原文地址:https://www.cnblogs.com/zhenjing/p/1864336.html
Copyright © 2011-2022 走看看