构造、析构与复制成员函数
● 单一参数构造函数
– 可以视为一种类型转换函数
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++基础与深度解析