C++类基础(六)

构造、析构与复制成员函数
● 单一参数构造函数
– 可以视为一种类型转换函数

struct Str
{
    Str(int x)
        : val(x)
    {
        std::cout << val << '\n';
    }
private:
    int val;
};

void fun(Str m)
{
    std::cout << "void fun(Str m)\n";
}
int main()
{
    Str m(3);
    Str m2 = 4; //=实现了从int类型到Str类型的转换
    fun(5); //fun()函数也实现了从int类型到Str类型的转换
    return 0;
}

在这里插入图片描述

– 可以使用 explicit 关键字避免求值过程中的隐式转换

struct Str
{
    explicit Str(int x)
        : val(x)
    {
        std::cout << val << '\n';
    }
private:
    int val;
};

void fun(Str m)
{
    std::cout << "void fun(Str m)\n";
}
void foo(int x)
{
    std::cout << "void foo(int x)\t";
}
int main()
{
    Str m(3); //OK
    Str m2{3}; //OK
    Str m3 = Str(3); //OK
    Str m4 = Str{3}; //OK
    Str m5 = 4; //Error: No viable conversion from 'int' to 'Str'
    fun(5); //Error: No matching function for call to 'fun'
    fun(Str(5)); //OK
    fun(Str{5}); //OK
    Str m6 = static_cast<Str>(3) //OK
    return 0;
}

● 拷贝构造函数:接收一个当前类对象的构造函数.

struct Str
{
    Str() = default;
    Str(const Str& x) //拷贝构造函数。C++标准: 当存在一个构造函数时(即使是拷贝构造函数),编译器拒绝合成缺省构造函数
        : val(x.val)
    {
        std::cout << "Str(const Str& x)" << '\n';
    }
private:
    int val = 3; //类内初始化
};
int main()
{
    Str m; //当只有拷贝构造函数而没有缺省构造函数时Error:  No matching constructor for initialization of 'Str';当添加缺省构造函数时OK
    Str m2(m); //OK
    Str m3 = m; //OK
    Str m4 {m}; //OK
    return 0;
}

在这里插入图片描述

– 会在涉及到拷贝初始化的场景被调用,比如:参数传递。因此要注意拷贝构造函数的形参类型

struct Str
{
    Str() = default;
    Str(const Str& x) //拷贝构造函数,注意形参声明为const类型的引用。C++标准: 当存在一个构造函数时(即使是拷贝构造函数),编译器拒绝合成缺省构造函数
    //Str(Str x) //Error: Copy constructor must pass its first argument by reference. 实参传入到形参时调用拷贝构造函数,导致递归调用
    //Str(Str& x) //不理想,函数可能对传入的实参进行修改,所以添加const限定符表示函数不会修改实参的值
        : val(x.val)
    {
        std::cout << "Str(const Str& x)" << '\n';
    }
private:
    int val = 3; //类内初始化
};
int main()
{
    Str m; //当只有拷贝构造函数而没有缺省构造函数时Error:  No matching constructor for initialization of 'Str';当添加缺省构造函数时OK
    std::cout << "Start copy construction:\n";
    Str m2(m); //OK
    Str m3 = m; //OK
    Str m4 {m}; //OK
    return 0;
}

在这里插入图片描述

– 如果未显式提供,那么编译器会自动合成一个,合成的版本会依次对每个数据成员调用拷贝构造

struct Str
{
    int val;
    char* string; //注意动态内存申请时的浅拷贝
};
int main()
{
    Str m;
    Str m2 = m; //OK
    return 0;
}
struct Str
{
    Str(const Str&) = default;
    int val;
    char* string; //注意动态内存申请时的浅拷贝
};
int main()
{
    Str m; //Error: No matching constructor for initialization of 'Str'
    return 0;
}
struct Str
{
    Str() = default; //无需书写形参的名称
    Str(const Str&) = default;
    int val;
    char* string;
};
int main()
{
    Str m; //OK
    Str m2 = m; //OK
    return 0;
}

● 移动构造函数 (C++11) :接收一个当前类右值引用对象的构造函数

int main()
{
    std::string ori("abc");
    std::string newStr = ori;
    std::cout << newStr << std::endl;
    std::cout << ori << std::endl;
    std::string newStr2 = std::move(ori); //移动构造: 将ori里面的内容转给了newStr2
    std::cout << newStr2 << std::endl;
    std::cout << ori << std::endl; //ori为空
    return 0;
}

在这里插入图片描述

– 可以从输入对象中“ ” 偷窃 资源,只要确保传入对象处于合法状态即可

struct Str
{
    Str() = default;
    Str(const Str&) = default;
    Str(Str&& x) //移动构造函数,注意形参声明为本类型的右值引用,因为当前操作存在对形参的写操作,所以不能有const限定符
        :val(x.val)
        ,a(std::move(x.a))
    {}
    void fun()
    {
        std::cout << val << '\t' << a <<std::endl;
    }
    int val = 3;
    std::string a = "abc";
};
int main()
{
    Str m;
    m.fun(); //输出3 abc
    Str m2 = std::move(m);
    m.fun(); //输出3
    return 0;
}

在这里插入图片描述

– 当某些特殊成员函数(如拷贝构造)未定义时,编译器可以合成一个

struct Str2
{
    Str2(const Str2&) //#4,定义了拷贝构造函数,编译器不会为这个类自动合成一个移动构造函数,也不会自动合成一个缺省构造函数
    {
        std::cout<< "Str2::Str2(const Str2&)\n" ;
    }
};

struct Str
{
    Str() = default; //使用编译器缺省的行为,此处编译器不能自动合成缺省构造函数,因为Str2没有缺省构造函数(见#4)
                     //#3,Error: default constructor of 'Str' is implicitly deleted because field 'm_str' has no default constructor
    Str(const Str&) = default;
    Str(Str&& x) = default;
    int val = 3;
    std::string a = "abc";
    Str2 m_str; //#2,Error: explicitly defaulted function was implicitly deleted here
};
int main()
{
    //Str2中没有定义缺省构造函数导致#1、#2和#3处的Error
    Str m; //#1,Error: Call to implicitly-deleted default constructor of 'Str'
    Str m2 = std::move(m);
    return 0;
}
struct Str2
{
    Str2() = default;
    Str2(const Str2&)
    {
        std::cout<< "Str2::Str2(const Str2&)\n" ;
    }
};

struct Str
{
    Str() = default;
    Str(const Str&) = default;
    Str(Str&& x) = default; //首先: 移动构造函数是有效的
    int val = 3;
    std::string a = "abc";
    Str2 m_str; //同时: 类Str的数据成员包含另一个用户自定义类型Str2,并且Str2类内只定义了拷贝构造函数没有定义移动构造函数
};
int main()
{
    Str m;
    Str m2 = std::move(m); //那么: 类Str的移动构造函数在处理数据成员(Str2)m_str时,会调用类Str2的拷贝构造函数
    return 0;
}

在这里插入图片描述

struct Str2
{
    Str2() = default;
    Str2(const Str2&)
    {
        std::cout<< "Str2::Str2(const Str2&)\n" ;
    }
    Str2(Str2&&)
    {
        std::cout << "Str2::Str2(Str2&&)\n";
    }
};

struct Str
{
    Str() = default;
    Str(const Str&) = default;
    Str(Str&& x) = default; //首先: 移动构造函数是有效的
    int val = 3;
    std::string a = "abc";
    Str2 m_str; //同时: 类Str的数据成员包括另一个用户自定义类型Str2,Str2类内定义了拷贝构造函数和移动构造函数
};
int main()
{
    Str m;
    Str m2 = std::move(m); //那么: 类Str的移动构造函数在处理数据成员(Str2)m_str时,会调用类Str2的移动构造函数(换言之,类Str2中有移动调移动,没移动调拷贝)
    return 0;
}

在这里插入图片描述

– 通常声明为不可抛出异常的函数

struct Str2
{
    Str2() = default;
    Str2(const Str2&) //拷贝构造函数可能会抛出异常
    {
        std::cout<< "Str2::Str2(const Str2&)\n" ;
    }
};

struct Str
{
    Str() = default;
    Str(const Str&) = default;
    Str(Str&& x) noexcept = default; //但是将移动构造函数声明为不会抛出异常,可能导致系统崩溃
    int val = 3;
    std::string a = "abc";
    Str2 m_str;
};
int main()
{
    Str m;
    Str m2 = std::move(m);
    return 0;
}
struct Str2
{
    Str2() = default;
    Str2(const Str2&)
    {
        std::cout<< "Str2::Str2(const Str2&)\n" ;
    }
    Str2(Str2&&) noexcept //OK
    {
        std::cout << "Str2::Str2(Str2&&)\n";
    }
};

struct Str
{
    Str() = default;
    Str(const Str&) = default;
    Str(Str&& x) noexcept = default; //OK, 编译器不会引入相应的异常逻辑,保证了不降低性能;更重要地,很多数据结构会在保证移动构造函数不会抛出异常的时候才会去使用它
    int val = 3;
    std::string a = "abc";
    Str2 m_str;
};
int main()
{
    Str m;
    Str m2 = std::move(m);
    return 0;
}

– 注意右值引用对象用做表达式时是左值!

struct Str
{
    Str() = default;
    Str(const Str&) = default;
    Str(Str&& x) noexcept //x声明为右值引用
    {
        std::string tmp = x.a; //x被视为左值来使用,没有“偷掉”x的数据成员std::string a
        std::cout << x.a <<std::endl;
        std::string tmp2 = std::move(x.a); //“偷掉”了x的数据成员std::string a
        std::cout << x.a <<std::endl;
    }
    int val = 3;
    std::string a = "abc";
};
int main()
{
    Str m;
    Str m2 = std::move(m);
    return 0;
}

在这里插入图片描述

struct Str
{
    Str(int input, std::string str)
    {
        val = input;
        a = str;
    }
    Str(const Str&) = default;
    Str(Str&& x) noexcept
    {
        std::string tmp = x.a;
        std::cout << x.a <<std::endl;
        std::string tmp2 = std::move(x.a);
        std::cout << x.a <<std::endl;
    }
    int val = 3;
    std::string a = "abc";
};
void fun(Str&& x) //x声明为右值引用,普通函数中也被视为左值来使用
{
    std::cout << x.a <<std::endl;
    std::string tmp2 = std::move(x.a); //也“偷掉”了x的数据成员std::string a
    std::cout << x.a <<std::endl;
}
int main()
{
    fun(Str(5, "mystring"));
    return 0;
}

在这里插入图片描述

参考
深蓝学院:C++基础与深度解析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值