C++基础教程面向对象(学习笔记(31))

转换构造函数, explicit和delete

默认情况下,C ++会将任何构造函数视为隐式转换运算符。考虑以下情况:

#include <cassert>
#include <iostream>
 
class Fraction
{
private:
	int m_numerator;
	int m_denominator;
 
public:
    // 默认构造函数
    Fraction(int numerator=0, int denominator=1) :
        m_numerator(numerator), m_denominator(denominator)
    {
        assert(denominator != 0);
    }
 
        // 拷贝构造函数
	Fraction(const Fraction &copy) :
		m_numerator(copy.m_numerator), m_denominator(copy.m_denominator)
	{
		// 这里不需要检查0的分母,因为副本必须已经是有效的分数
		std::cout << "Copy constructor called\n"; // just to prove it works
	}
 
	friend std::ostream& operator<<(std::ostream& out, const Fraction &f1);
        int getNumerator() { return m_numerator; }
        void setNumerator(int numerator) { m_numerator = numerator; }
};
 
std::ostream& operator<<(std::ostream& out, const Fraction &f1)
{
	out << f1.m_numerator << "/" << f1.m_denominator;
	return out;
}
 
Fraction makeNegative(Fraction f)
{
    f.setNumerator(-f.getNumerator());
    return f;
}
 
int main()
{
    std::cout << makeNegative(6); // 注意这里的整数
 
    return 0;
}

虽然函数makeNegative()期望一个Fraction,但我们已经给它整数字6。因为Fraction有一个愿意接受单个整数的构造函数,所以编译器会隐式地将文字6转换为Fraction对象。它通过使用Fraction(int,int)构造函数初始化makeNegative()参数f来完成此操作。

由于f已经是一个Fraction,makeNegative()的返回值被拷贝构造回main,然后将其传递给重载的operator <<。

因此,上面的程序打印:
Copy constructor called
-6/1
这种隐式转换适用于所有类型的初始化(直接,统一和拷贝)。
适用于隐式转换的构造函数称为转换构造函数。在C ++ 11之前,只有采用一个参数的构造函数才能转换构造函数。但是,使用C ++ 11中新的统一初始化语法,这个限制被取消了,并且采用多个参数的构造函数现在可以转换构造函数。

explicit关键字

虽然在Fraction案例中进行隐式转换是有意义的,但在其他情况下,这可能是不合需要的,或导致意外行为:

#include <string>
#include <iostream>
 
class MyString
{
private:
	std::string m_string;
public:
	MyString(int x) // 分配大小为x的字符串
	{
		m_string.resize(x);
	}
 
	MyString(const char *string) //分配字符串来保存字符串值
	{
		m_string = string;
	}
 
	friend std::ostream& operator<<(std::ostream& out, const MyString &s);
 
};
 
std::ostream& operator<<(std::ostream& out, const MyString &s)
{
	out << s.m_string;
	return out;
}
 
int main()
{
	MyString mine = 'x'; // 使用MyString的拷贝初始化
	std::cout << mine;
	return 0;
}

在上面的示例中,用户尝试使用char初始化字符串。因为chars是整数族的一部分,所以编译器将使用转换构造函数MyString(int)构造函数将char隐式转换为MyString。然后程序将打印此MyString,以获得意外结果。
解决此问题的一种方法是通过显式关键字使构造函数(和转换函数)显式化,该关键字放在构造函数名称的前面。显式构造函数和转换函数不会用于隐式转换或拷贝初始化:

#include <string>
#include <iostream>
 
class MyString
{
private:
	std::string m_string;
public:
        // explicit关键字使得此构造函数不符合隐式转换的条件
	explicit MyString(int x) // 分配大小为x的字符串
	{
		m_string.resize(x);
	}
 
	MyString(const char *string) // 分配字符串来保存字符串值
	{
		m_string = string;
	}
 
	friend std::ostream& operator<<(std::ostream& out, const MyString &s);
 
};
 
std::ostream& operator<<(std::ostream& out, const MyString &s)
{
	out << s.m_string;
	return out;
}
 
int main()
{
	MyString mine = 'x'; // 编译错误,因为MyString(int)现在是explicit,没有任何东西可以匹配它
	std::cout << mine;
	return 0;
}

上面的程序不会编译,因为MyString(int)是显式的,并且找不到合适的转换构造函数来隐式地将’x’转换为MyString。
但请注意,仅使构造函数显式可以防止隐式转换。仍然允许显式转换(通过转换):

std::cout << static_cast<MyString>(5); // 允许:explicit 转换为5到MyString(int)

直接或统一初始化仍然会将参数转换为匹配(统一初始化不会缩小转换范围,但它会很乐意进行其他类型的转换)。

MyString str('x'); // 允许:初始化参数仍可以隐式转换为匹配

规则:考虑使构造函数和用户定义的转换成员函数显式化以防止隐式转换错误

在C ++ 11中,explicit关键字也可以与转换运算符一起使用。

delete关键字

在我们的MyString情况下,我们真的想完全禁止将’x’转换为MyString(无论是隐式还是显式,因为结果不是直观的)。部分执行此操作的一种方法是添加MyString(char)构造函数,并将其设为私有:

#include <string>
#include <iostream>
 
class MyString
{
private:
	std::string m_string;
 
        MyString(char) // MyString(char)类型的对象不能从类外部构造
        {
        }
public:
        // explicit使得此构造函数不符合隐式转换的条件
	explicit MyString(int x) //分配大小为x 的字符串
	{
		m_string.resize(x);
	}
 
	MyString(const char *string) // 分配字符串来保存字符串值
	{
		m_string = string;
	}
 
	friend std::ostream& operator<<(std::ostream& out, const MyString &s);
 
};
 
std::ostream& operator<<(std::ostream& out, const MyString &s)
{
	out << s.m_string;
	return out;
}
 
int main()
{
	MyString mine('x'); // 编译错误,因为MyString(char)是私有的
	std::cout << mine;
	return 0;
}

但是,仍然可以在类内部使用此构造函数(私有访问仅阻止非成员调用此函数)。
解决此问题的更好方法是使用“delete”关键字(在C ++ 11中引入)来删除该函数:

#include <string>
#include <iostream>
 
class MyString
{
private:
	std::string m_string;
 
public:
        MyString(char) = delete; // 任何使用此构造函数都是错误的
 
        // explicit使得此构造函数不符合隐式转换的条件
	explicit MyString(int x) //分配大小为x 的字符串
	{
		m_string.resize(x);
	}
 
	MyString(const char *string) // 分配字符串来保存字符串值
	{
		m_string = string;
	}
 
	friend std::ostream& operator<<(std::ostream& out, const MyString &s);
 
};
 
std::ostream& operator<<(std::ostream& out, const MyString &s)
{
	out << s.m_string;
	return out;
}
 
int main()
{
	MyString mine('x'); // 编译错误,因为MyString(char)被删除了
	std::cout << mine;
	return 0;
}

delete函数后,任何对该函数的使用都被视为编译错误。

请注意,也可以删除拷贝构造函数和重载运算符,以防止使用这些函数。

猜你喜欢

转载自blog.csdn.net/qq_41879485/article/details/83243417