c++类的构造函数不显式声明会自动生成吗

说明一下,我用的是g++7.1.0编译器,标准库源代码也是这个版本的。

本篇文章讲解c++11中,类的构造函数种类,以及不显式声明的情况下是否会自动生成。

1. 类的构造函数类别

在我刚接触c++的时候,我一直知道类可以有四种形式的构造函数,即无参构造函数、有参构造函数、拷贝构造函数、赋值运算符构造函数,最近看标准IO源代码,发现又多了一种,那就是移动构造函数,这是c++11中补充进来的,所以现在c++可以拥有四种形式的构造函数,即无参构造函数、有参构造函数、拷贝构造函数、赋值构造函数、移动构造函数、移动赋值构造函数。

说明一下:赋值运算符operator=到底算不算构造函数,这个个人有个人的看法,不多讨论,但是单就说明构造函数的时候把它漏掉的话,我觉得有点耍流氓了,所以也要把它列进来。

一个完整的声明了这六种构造函数以及使用的案例如下:

  
#include <iostream>  
#include <string.h>  
using namespace std;  
  
class CPtr  
{  
    private:  
        char *m_pData;  
        int m_iSize;  
    public:  
        //without param constructors  
        CPtr()  
        {  
            m_iSize = 1024;  
            m_pData = new char[m_iSize];  
        }  
        ~CPtr()  
        {  
            if ( m_pData != nullptr )  
            {  
                delete []m_pData;  
                m_pData = nullptr;  
            }  
        }  
        //with param constructors  
        CPtr(const int p_iSize)  
        {  
            m_iSize = p_iSize;  
            m_pData = new char[p_iSize];  
        }  
        //copy constructors  
        CPtr(const CPtr& ptr)  
        {  
            if (ptr.m_pData != nullptr)  
            {  
                m_iSize = strlen(ptr.m_pData)+1;  
                m_pData = new char[m_iSize];  
                strncpy(m_pData, ptr.m_pData, m_iSize-1);  
            }  
        }  
        //move constructors  
        CPtr(CPtr&& ptr)  
        {  
            m_pData = ptr.m_pData;  
            m_iSize = ptr.m_iSize;  
            ptr.m_pData = nullptr;  
            ptr.m_iSize = 0;  
        }  
        //赋值构造函数  
        CPtr& operator=(const CPtr& ptr)  
        {  
            if (ptr.m_pData != nullptr)  
            {  
                m_iSize = strlen(ptr.m_pData)+1;  
                m_pData = new char[m_iSize];  
                strncpy(m_pData, ptr.m_pData, m_iSize-1);  
            }  
            return *this;  
        }     
        //移动赋值构造函数  
        CPtr& operator=(CPtr&& ptr)  
        {  
            m_pData = ptr.m_pData;  
            m_iSize = ptr.m_iSize;  
            ptr.m_pData = nullptr;  
            ptr.m_iSize = 0;  
            return *this;  
        }  
        void setData(const char* str)  
        {  
            if (  str == nullptr)  
            {  
                cout << "str is nullptr" << endl;  
                return;  
            }  
            if ( m_iSize == 0)  
            {  
                cout << "the memory is nothing" << endl;  
                return;  
            }  
            int iSize = strlen(str);  
            if ( iSize < m_iSize )  
            {  
                strncpy(m_pData, str, iSize);  
            }  
            else  
            {  
                strncpy(m_pData, str, m_iSize-1);  
            }  
        }  
        void print(const char* object)  
        {  
            cout << object << "'s data is " << m_pData << endl;  
        }  
};  
  
int main()  
{  
    CPtr p1(1024);  
    p1.setData("lilei and hanmeimei");  
    p1.print("p1");  
    CPtr p2(p1);  
    p2.print("p2");  
    CPtr p3 = p1;  
    p3.print("p3");  
    CPtr p4(move(p1));  
    p4.print("p4");  
    CPtr p5 = move(p2);  
    p5.print("p5");  
    return 0;  
}  

这里move是标准库的一个函数,返回一个右值引用,也就是CPtr &&类型。

这里我们是显示声明了所有的构造函数,接下来看看编译器对于class构造函数的隐式生成规则。

2. 构造函数默认生成规则

2.1 没有显式声明任何构造函数

编译器会自动生成默认的无参构造函数,这一点我们是可以肯定的,那另外几种构造函数也会默认生成吗,这个就不太确定了。

我们把上面那段代码修改一下,如下:

  
#include <iostream>  
#include <string.h>  
using namespace std;  
  
class CPtr  
{  
    private:  
        char *m_pData;  
        int m_iSize;  
    public:  
        //without param constructors  
        /*  
        CPtr()  
        {  
            m\_iSize = 1024;  
            m\_pData = new char[m\_iSize];  
        }  
        ~CPtr()  
        {  
            if ( m\_pData != nullptr )  
            {  
                delete []m\_pData;  
                m\_pData = nullptr;  
            }  
        }  
        //with param constructors  
        CPtr(const int p\_iSize)  
        {  
            m\_iSize = p\_iSize;  
            m\_pData = new char[p\_iSize];  
        }  
        //copy constructors  
        CPtr(const CPtr& ptr)  
        {  
            if (ptr.m\_pData != nullptr)  
            {  
                m\_iSize = strlen(ptr.m\_pData)+1;  
                m\_pData = new char[m\_iSize];  
                strncpy(m\_pData, ptr.m\_pData, m\_iSize-1);  
            }  
        }  
        //move constructors  
        CPtr(CPtr&& ptr)  
        {  
            m\_pData = ptr.m\_pData;  
            m\_iSize = ptr.m\_iSize;  
            ptr.m\_pData = nullptr;  
            ptr.m\_iSize = 0;  
        }  
        //赋值构造函数  
        CPtr& operator=(const CPtr& ptr)  
        {  
            if (ptr.m\_pData != nullptr)  
            {  
                m\_iSize = strlen(ptr.m\_pData)+1;  
                m\_pData = new char[m\_iSize];  
                strncpy(m\_pData, ptr.m\_pData, m\_iSize-1);  
            }  
            return *this;  
        }     
        //移动赋值构造函数  
        CPtr& operator=(CPtr&& ptr)  
        {  
            m\_pData = ptr.m\_pData;  
            m\_iSize = ptr.m\_iSize;  
            ptr.m\_pData = nullptr;  
            ptr.m\_iSize = 0;  
            return *this;  
        }  
        */  
        void setData(const char* str)  
        {  
            if (  str == nullptr)  
            {  
                cout << "str is nullptr" << endl;  
                return;  
            }  
            if ( m_iSize == 0)  
            {  
                cout << "the memory is nothing" << endl;  
                return;  
            }  
            int iSize = strlen(str);  
            if ( iSize < m_iSize )  
            {  
                strncpy(m_pData, str, iSize);  
            }  
            else  
            {  
                strncpy(m_pData, str, m_iSize-1);  
            }  
        }  
        void print(const char* object)  
        {  
            cout << object << "'s data is " << m_pData << endl;  
        }  
};  
  
int main()  
{  
    CPtr p1;  
    //p1.setData("lilei and hanmeimei");  
    //p1.print("p1");  
    CPtr p2(p1);  
    //p2.print("p2");  
    CPtr p3 = p1;  
    //p3.print("p3");  
    CPtr p4(move(p1));  
    //p4.print("p4");  
    CPtr p5 = move(p2);  
    //p5.print("p5");  
    CPtr p6(1024);  
    return 0;  
}  

把所有的构造函数都注释掉,然后对上述代码进行编译,报错,报错信息如下:

  
test.cpp: 在函数‘int main()’中:  
test.cpp:110:14: 错误:no matching function for call to ‘CPtr::CPtr(int)’  
  CPtr p6(1024);  
              ^  
test.cpp:5:7: 附注:candidate: CPtr::CPtr()  
 class CPtr  
       ^~~~  
test.cpp:5:7: 附注: 备选需要 0 实参,但提供了 1 个  
test.cpp:5:7: 附注:candidate: constexpr CPtr::CPtr(const CPtr&)  
test.cpp:5:7: 附注:  no known conversion for argument 1 from ‘int’ toconst CPtr&’  
test.cpp:5:7: 附注:candidate: constexpr CPtr::CPtr(CPtr&&)  
test.cpp:5:7: 附注:  no known conversion for argument 1 from ‘int’ to ‘CPtr&&’  

从错误信息我们可以看到两点,一是带int类型参数的构造函数是不会自动生成的,二是类CPtr是存在拷贝构造和移动构造的,接着我们现在把p6那一行注释掉,再编译,就通过了,也就是说对于class类型,当没有显式声明任何构造函数的时候,编译器除了默认生成无参构造函数以外,还会自动生成拷贝构造函数、赋值构造函数、移动构造函数、移动赋值构造函数,并且自动生成的构造函数都是public的,因为它们是可以用于生成对象的,而对于有参构造函数,因为参数是未知的,所以编译器没有办法自动生成。

也就是说,当没有显式声明任何构造函数时,会默认生成五种构造函数,即:普通构造函数、拷贝构造函数、赋值构造函数、移动构造函数、移动赋值构造函数,而对于有参构造,除非显式指定,否则任务情况下不会自动生成。

2.2 显式声明普通构造函数

还是之前的代码,再修改一下:

  
#include <iostream>  
#include <string.h>  
using namespace std;  
  
class CPtr  
{  
    private:  
        char *m_pData;  
        int m_iSize;  
    public:  
        //without param constructors  
        CPtr()  
        {  
            m_iSize = 1024;  
            m_pData = new char[m_iSize];  
        }  
        ~CPtr()  
        {  
            if ( m_pData != nullptr )  
            {  
                delete []m_pData;  
                m_pData = nullptr;  
            }  
        }  
        /*  
        //with param constructors  
        CPtr(const int p\_iSize)  
        {  
            m\_iSize = p\_iSize;  
            m\_pData = new char[p\_iSize];  
        }  
        //copy constructors  
        CPtr(const CPtr& ptr)  
        {  
            if (ptr.m\_pData != nullptr)  
            {  
                m\_iSize = strlen(ptr.m\_pData)+1;  
                m\_pData = new char[m\_iSize];  
                strncpy(m\_pData, ptr.m\_pData, m\_iSize-1);  
            }  
        }  
        //move constructors  
        CPtr(CPtr&& ptr)  
        {  
            m\_pData = ptr.m\_pData;  
            m\_iSize = ptr.m\_iSize;  
            ptr.m\_pData = nullptr;  
            ptr.m\_iSize = 0;  
        }  
        //赋值构造函数  
        CPtr& operator=(const CPtr& ptr)  
        {  
            if (ptr.m\_pData != nullptr)  
            {  
                m\_iSize = strlen(ptr.m\_pData)+1;  
                m\_pData = new char[m\_iSize];  
                strncpy(m\_pData, ptr.m\_pData, m\_iSize-1);  
            }  
            return *this;  
        }     
        //移动赋值构造函数  
        CPtr& operator=(CPtr&& ptr)  
        {  
            m\_pData = ptr.m\_pData;  
            m\_iSize = ptr.m\_iSize;  
            ptr.m\_pData = nullptr;  
            ptr.m\_iSize = 0;  
            return *this;  
        }  
        */  
        void setData(const char* str)  
        {  
            if (  str == nullptr)  
            {  
                cout << "str is nullptr" << endl;  
                return;  
            }  
            if ( m_iSize == 0)  
            {  
                cout << "the memory is nothing" << endl;  
                return;  
            }  
            int iSize = strlen(str);  
            if ( iSize < m_iSize )  
            {  
                strncpy(m_pData, str, iSize);  
            }  
            else  
            {  
                strncpy(m_pData, str, m_iSize-1);  
            }  
        }  
        void print(const char* object)  
        {  
            cout << object << "'s data is " << m_pData << endl;  
        }  
};  
  
int main()  
{  
    CPtr p1;  
    //p1.setData("lilei and hanmeimei");  
    //p1.print("p1");  
    CPtr p2(p1);  
    //p2.print("p2");  
    CPtr p3 = p1;  
    //p3.print("p3");  
    CPtr p4(move(p1));  
    //p4.print("p4");  
    CPtr p5 = move(p2);  
    //p5.print("p5");  
    return 0;  
}  

上面的代码编译通过,说明当只显示声明了普通构造函数的时候,会自动生成拷贝构造函数、赋值构造函数、移动构造函数、移动赋值构造函数这四种。

2.3 显式声明拷贝构造函数

首先看只显式声明一个拷贝构造函数的情况,如下:

  
#include <iostream>  
#include <string.h>  
using namespace std;  
  
class CPtr  
{  
    private:  
        char *m_pData;  
        int m_iSize;  
    public:  
        //without param constructors  
        /*  
        CPtr()  
        {  
            m\_iSize = 1024;  
            m\_pData = new char[m\_iSize];  
        }  
        */  
        ~CPtr()  
        {  
            if ( m_pData != nullptr )  
            {  
                delete []m_pData;  
                m_pData = nullptr;  
            }  
        }  
        /*  
        //with param constructors  
        CPtr(const int p\_iSize)  
        {  
            m\_iSize = p\_iSize;  
            m\_pData = new char[p\_iSize];  
        }  
        */  
        //copy constructors  
        CPtr(const CPtr& ptr)  
        {  
            if (ptr.m_pData != nullptr)  
            {  
                m_iSize = strlen(ptr.m_pData)+1;  
                m_pData = new char[m_iSize];  
                strncpy(m_pData, ptr.m_pData, m_iSize-1);  
            }  
        }  
        //move constructors  
        /*  
        CPtr(CPtr&& ptr)  
        {  
            m\_pData = ptr.m\_pData;  
            m\_iSize = ptr.m\_iSize;  
            ptr.m\_pData = nullptr;  
            ptr.m\_iSize = 0;  
        }  
        */  
        //赋值构造函数  
        /*  
        CPtr& operator=(const CPtr& ptr)  
        {  
            if (ptr.m\_pData != nullptr)  
            {  
                m\_iSize = strlen(ptr.m\_pData)+1;  
                m\_pData = new char[m\_iSize];  
                strncpy(m\_pData, ptr.m\_pData, m\_iSize-1);  
            }  
            return *this;  
        }     
        */  
        //移动赋值构造函数  
        /*  
        CPtr& operator=(CPtr&& ptr)  
        {  
            m\_pData = ptr.m\_pData;  
            m\_iSize = ptr.m\_iSize;  
            ptr.m\_pData = nullptr;  
            ptr.m\_iSize = 0;  
            return *this;  
        }  
        */  
        void setData(const char* str)  
        {  
            if (  str == nullptr)  
            {  
                cout << "str is nullptr" << endl;  
                return;  
            }  
            if ( m_iSize == 0)  
            {  
                cout << "the memory is nothing" << endl;  
                return;  
            }  
            int iSize = strlen(str);  
            if ( iSize < m_iSize )  
            {  
                strncpy(m_pData, str, iSize);  
            }  
            else  
            {  
                strncpy(m_pData, str, m_iSize-1);  
            }  
        }  
        void print(const char* object)  
        {  
            cout << object << "'s data is " << m_pData << endl;  
        }  
};  
  
int main()  
{  
    CPtr p1;  
    //p1.setData("lilei and hanmeimei");  
    //p1.print("p1");  
    CPtr p2(p1);  
    //p2.print("p2");  
    CPtr p3 = p1;  
    //p3.print("p3");  
    CPtr p4(move(p1));  
    //p4.print("p4");  
    CPtr p5 = move(p2);  
    //p5.print("p5");  
    return 0;  
}  

编译报错如下:

  
test.cpp: 在函数‘int main()’中:  
test.cpp:107:7: 错误:no matching function for call to ‘CPtr::CPtr()’  
  CPtr p1;  
       ^~  
test.cpp:36:3: 附注:candidate: CPtr::CPtr(const CPtr&)  
   CPtr(const CPtr& ptr)  
   ^~~~  
test.cpp:36:3: 附注: 备选需要 1 实参,但提供了 0

说明当只声明拷贝构造函数时,连默认构造都不复存在,就没有办法声明第一个对象,这样肯定是不行的,接下来取消对于默认构造函数的注释,编译就通过了,接下来再取消对于赋值构造函数的注释,编译还是可以通过。

也就是说当只声明拷贝构造函数的时候,其他构造包括普通构造都不会自动生成,而当声明了普通构造和拷贝构造时,移动构造会自动生成。

3. 构造函数自动生成总结

总结一下,构造函数自动生成的规则:

  • 没有显式声明任何构造函数时,会自动生成普通构造函数、拷贝构造函数、赋值构造函数、移动构造函数、移动赋值构造函数五种;
  • 对于带普通参数的构造函数,任何情况下都不会自动生成;
  • 显式声明普通构造函数时,会自动生成拷贝构造函数、赋值构造函数、移动构造函数、移动赋值构造函数四种;
  • 只显式声明拷贝构造函数时,普通构造函数都不会自动生成,没有办法生成对象;
  • 显示声明普通构造函数和拷贝构造函数时,会自动生成移动构造函数;

这些构造函数不要求总是全部显式声明,但我们在使用class的时候最好显式声明这五种构造函数,避免出现一些不必要的问题。

picture.image

0
0
0
0
评论
未登录
暂无评论