显式关键字在C++中意味着什么?
当前回答
其他答案缺少一个重要因素,我将在这里提及。
除了“delete”关键字,“explicit”允许您控制编译器生成特殊成员函数的方式——默认构造函数、复制构造函数、复制赋值运算符、析构函数、移动构造函数和移动赋值。
参考https://learn.microsoft.com/en-us/cpp/cpp/explicitly-defaulted-and-deleted-functions
其他回答
显式关键字将构造函数转换为非转换构造函数。因此,代码不太容易出错。
在C++中,只有一个必需参数的构造函数被视为隐式转换函数。它将参数类型转换为类类型。这是否是一件好事取决于构造函数的语义。
例如,如果您有一个带有构造函数string(constchar*s)的字符串类,这可能正是您想要的。您可以将constchar*传递给需要String的函数,编译器将自动为您构造一个临时String对象。
另一方面,如果您有一个缓冲类,其构造函数buffer(int size)以字节为单位表示缓冲区的大小,那么您可能不希望编译器悄悄地将int转换为Buffers。为了防止这种情况,可以使用显式关键字声明构造函数:
class Buffer { explicit Buffer(int size); ... }
这样,
void useBuffer(Buffer& buf);
useBuffer(4);
成为编译时错误。如果要传递临时缓冲区对象,必须显式执行以下操作:
useBuffer(Buffer(4));
总之,如果单参数构造函数将参数转换为类的对象,则可能不希望使用显式关键字。但是,如果您有一个构造函数恰好接受一个参数,那么应该将其声明为显式的,以防止编译器意外地进行转换。
这个答案是关于带/不带显式构造函数的对象创建的,因为其他答案中没有涉及它。
考虑以下没有显式构造函数的类:
class Foo
{
public:
Foo(int x) : m_x(x)
{
}
private:
int m_x;
};
Foo类的对象可以通过两种方式创建:
Foo bar1(10);
Foo bar2 = 20;
根据实现的不同,实例化类Foo的第二种方式可能会令人困惑,或者不是程序员想要的。将显式关键字前缀到构造函数将在Foobar2=20;处生成编译器错误;。
通常最好将单参数构造函数声明为显式的,除非您的实现明确禁止。
还请注意,具有
所有参数的默认参数,或第二个参数的默认参数
都可以用作单参数构造函数。因此,您可能希望将这些也明确化。
例如,如果您正在创建一个函子(请查看此答案中声明的“add_x”结构),那么您可能会故意不想使单参数构造函数显式。在这种情况下,创建add_x add30=30的对象;可能会有意义。
这里有一篇关于显式构造函数的好文章。
使单参数构造函数(包括具有arg2、arg3、…的默认值的构造函数)如前所述始终是一种良好的编码实践。就像C++一样:如果你不这样做,你会希望你这样做。。。
类的另一个好做法是将副本构造和赋值设为私有(也就是禁用它),除非你真的需要实现它。这避免了在使用C++默认为你创建的方法时,指针的最终副本。另一种方法是从boost::noncopyable派生。
关键字explicit伴随着
类X的构造函数,不能用于将第一个(仅限任何)参数隐式转换为类型X
C++〔class.conv.ctor〕1) 没有函数说明符显式声明的构造函数指定从其参数类型到其类类型的转换。这样的构造函数称为转换构造函数。2) 显式构造函数与非显式构造函数一样构造对象,但仅在显式使用直接初始化语法(8.5)或强制转换(5.2.9、5.4)的情况下才能这样做。默认构造函数可以是显式构造函数;这样的构造函数将用于执行默认初始化或值初始化(8.5).
或仅考虑用于直接初始化和显式转换的转换函数。
C++〔class.conv.fct〕2) 转换函数可以是显式的(7.1.2),在这种情况下,它仅被视为用于直接初始化的用户定义转换(8.5)。否则,用户定义转换不限于在赋值中使用和初始化。
概述
显式转换函数和构造函数只能用于显式转换(直接初始化或显式转换操作),而非显式构造函数和转换函数可以用于隐式和显式转换。
/*
explicit conversion implicit conversion
explicit constructor yes no
constructor yes yes
explicit conversion function yes no
conversion function yes yes
*/
使用结构X、Y、Z和函数foo、bar、baz的示例:
让我们看一个小的结构和函数设置,看看显式转换和非显式转换之间的区别。
struct Z { };
struct X {
explicit X(int a); // X can be constructed from int explicitly
explicit operator Z (); // X can be converted to Z explicitly
};
struct Y{
Y(int a); // int can be implicitly converted to Y
operator Z (); // Y can be implicitly converted to Z
};
void foo(X x) { }
void bar(Y y) { }
void baz(Z z) { }
关于构造函数的示例:
函数参数的转换:
foo(2); // error: no implicit conversion int to X possible
foo(X(2)); // OK: direct initialization: explicit conversion
foo(static_cast<X>(2)); // OK: explicit conversion
bar(2); // OK: implicit conversion via Y(int)
bar(Y(2)); // OK: direct initialization
bar(static_cast<Y>(2)); // OK: explicit conversion
对象初始化:
X x2 = 2; // error: no implicit conversion int to X possible
X x3(2); // OK: direct initialization
X x4 = X(2); // OK: direct initialization
X x5 = static_cast<X>(2); // OK: explicit conversion
Y y2 = 2; // OK: implicit conversion via Y(int)
Y y3(2); // OK: direct initialization
Y y4 = Y(2); // OK: direct initialization
Y y5 = static_cast<Y>(2); // OK: explicit conversion
转换函数示例:
X x1{ 0 };
Y y1{ 0 };
函数参数的转换:
baz(x1); // error: X not implicitly convertible to Z
baz(Z(x1)); // OK: explicit initialization
baz(static_cast<Z>(x1)); // OK: explicit conversion
baz(y1); // OK: implicit conversion via Y::operator Z()
baz(Z(y1)); // OK: direct initialization
baz(static_cast<Z>(y1)); // OK: explicit conversion
对象初始化:
Z z1 = x1; // error: X not implicitly convertible to Z
Z z2(x1); // OK: explicit initialization
Z z3 = Z(x1); // OK: explicit initialization
Z z4 = static_cast<Z>(x1); // OK: explicit conversion
Z z1 = y1; // OK: implicit conversion via Y::operator Z()
Z z2(y1); // OK: direct initialization
Z z3 = Z(y1); // OK: direct initialization
Z z4 = static_cast<Z>(y1); // OK: explicit conversion
为什么使用显式转换函数或构造函数?
转换构造函数和非显式转换函数可能会引入歧义。
考虑一个可转换为int的结构V,一个可从V隐式构造的结构U,以及一个分别为U和bool重载的函数f。
struct V {
operator bool() const { return true; }
};
struct U { U(V) { } };
void f(U) { }
void f(bool) { }
如果传递类型为V的对象,则对f的调用是不明确的。
V x;
f(x); // error: call of overloaded 'f(V&)' is ambiguous
编译器不知道是使用U的构造函数还是转换函数将V对象转换为传递给f的类型。
如果U的构造函数或V的转换函数是显式的,则不会有歧义,因为只考虑非显式转换。如果两者都是显式的,则必须使用显式转换或强制转换操作来使用类型V的对象调用f。
转换构造函数和非显式转换函数可能会导致意外行为。
考虑打印某个向量的函数:
void print_intvector(std::vector<int> const &v) { for (int x : v) std::cout << x << '\n'; }
如果向量的大小构造函数不是显式的,则可以这样调用函数:
print_intvector(3);
人们会从这样的电话中期待什么?一行包含3或三行包含0?(第二个是发生了什么。)
在类接口中使用显式关键字会强制接口的用户明确表示所需的转换。
正如Bjarne Stroustrup(在“C++编程语言”,第4版,35.2.1,第1011页)所说的,为什么std::duration不能从一个简单的数字隐式构建:
如果你知道你的意思,就明确地说出来。
推荐文章
- 为什么STL如此严重地基于模板而不是继承?
- 查找当前可执行文件的路径,不包含/proc/self/exe
- 未定义对静态constexpr char的引用[]
- 在c++中,restrict关键字是什么意思?
- c++中类似于java的instanceof
- include_directories和target_include_directories在CMake中的区别是什么?
- std::make_pair与std::pair的构造函数的目的是什么?
- 如何追加一个字符到std::字符串?
- 为什么要在c++中使用嵌套类?
- 如何处理11000行c++源文件?
- 使用g++编译多个.cpp和.h文件
- 如何在c++中追加文本到文本文件?
- 在c++中使用"super
- Mmap () vs.读取块
- 什么是不归路?