explicit
关键字在C++中的含义是什么?
explicit
关键字在C++中的含义是什么?
编译器允许进行一次隐式转换以解决函数参数。这意味着编译器可以使用可调用单个参数的构造函数将一种类型转换为另一种类型,以获取参数的正确类型。
这里是一个具有可以用于隐式转换的构造函数的示例类:
class Foo
{
private:
int m_foo;
public:
// single parameter constructor, can be used as an implicit conversion
Foo (int foo) : m_foo (foo) {}
int GetFoo () { return m_foo; }
};
这是一个简单的函数,它接受一个Foo
对象:
void DoBar (Foo foo)
{
int i = foo.GetFoo ();
}
这里是调用DoBar
函数的地方:
int main ()
{
DoBar (42);
}
参数不是一个Foo对象,而是一个int。 但是,存在一个以int为参数的构造函数可以将参数转换为正确的类型。explicit
,而是声明一个构造函数explicit
。但是,没错:你的类型为Foo
的参数必须显式地构造,不能仅仅通过将它们的构造函数参数插入到函数中就隐式地构造它们。 - Christian Severin假设你有一个类String
:
class String {
public:
String(int n); // allocate n bytes to the String object
String(const char *p); // initializes object with char *p
};
现在,如果您尝试:
String mystring = 'x';
字符'x'
将被隐式转换为int
,然后将调用String(int)
构造函数。但是,这不是用户可能想要的结果。因此,为了防止这种情况,我们应将构造函数定义为explicit
:
class String {
public:
explicit String (int n); //allocate n bytes
String(const char *p); // initialize sobject with string p
};
String s = {0};
变得不合法,而不是像 String s = 0;
那样尝试使用空指针调用另一个构造函数。 - Johannes Schaub - litbString mystring("x")
时错误地使用了 String mystring('x')
,您仍将遇到同样的错误,不是吗?此外,从上面的评论中,我们可以看出通过将构造函数的 int 形式设置为“explicit”可以改进使用 String s = 0
的行为,即使用 String s = {0}
。但是,除了了解构造函数的优先级之外,您如何知道 String s{0}
的意图(即如何发现错误)? - ArbalestString mystring('x');
,编译器仍然会调用String(int)
构造函数而不生成错误,正如@Arbalest指出的那样。explicit
关键字是为了防止直接初始化和函数解析中发生的隐式转换。对你的例子来说,更好的解决方案是简单地重载构造函数:String(char c);
。 - Géry Ogamexplicit
关键字,String mystring = 'x'
仍然会调用具有 int
类型的相同构造函数,是吗? - xyf在C++中,只有一个必需参数的构造函数被视为隐式转换函数。它将参数类型转换为类类型。这是否是一件好事取决于构造函数的语义。
例如,如果你有一个字符串类,它有一个名为String(const char* s)
的构造函数,那可能正是你想要的。你可以将一个const char*
传递给期望一个String
的函数,编译器会自动为你构造一个临时的String
对象。
另一方面,如果你有一个缓冲区类,它的构造函数Buffer(int size)
接收缓冲区大小(以字节为单位),你可能不希望编译器悄悄地将int
转换成Buffer
。为了防止这种情况发生,你需要使用explicit
关键字来声明构造函数:
class Buffer { explicit Buffer(int size); ... }
那样,
void useBuffer(Buffer& buf);
useBuffer(4);
变成了编译时错误。如果您想传递一个临时的 Buffer
对象,您必须明确地这样做:
变成了编译时错误。如果您想传递一个临时的Buffer
对象,则必须显式地进行传递:
useBuffer(Buffer(4));
总之,如果你的单参数构造函数将参数转换为类对象,那么你可能不想使用explicit
关键字。但是,如果你有一个仅仅需要一个参数的构造函数,你应该声明它为explicit
,以防止编译器意外地进行意想不到的转换。
useBuffer
函数需要一个左值作为其参数,useBuffer(Buffer(4))
也不能工作,因为它不是左值。将其改为接受 const Buffer&
或 Buffer&&
或只接受 Buffer
将使其工作。 - pqnetexplicit
的使用场景C++ [class.conv.ctor]
1) 没有显式指定“explicit”函数说明符的构造函数,会将其参数类型转换为该类的类型。这种构造函数称为转换构造函数。
2) 明确指定了“explicit”函数说明符的构造函数像非明确构造函数一样构造对象,但只能在直接初始化语法(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
的示例:让我们看一下一个小型结构体和函数设置,以了解explicit
和非explicit
转换之间的区别。
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
explicit
转换函数或构造函数?转换构造函数和非显式转换函数可能会引入歧义。
考虑一个结构体V
,可以转换为int
,一个从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
的转换函数有一个是 explicit
的,那么就不会有二义性,因为只会考虑非显式转换。如果两个都是显式的,那么使用类型为 V
的对象调用 f
就必须使用显式转换或强制转换操作。
转换构造函数和非显式转换函数可能导致意外的行为。
考虑一个打印一些向量的函数:
void print_intvector(std::vector<int> const &v) { for (int x : v) std::cout << x << '\n'; }
如果vector的大小构造函数不是显式的,那么就可以这样调用函数:print_intvector(3);
这个调用应该期望什么?一个包含3
的行,还是三个包含0
的行?(第二种情况是发生的。)
正如Bjarne Stroustrup在《C++程序设计语言》第四版35.2.1页1011所说,关于为什么std::duration
不能从纯数字隐式构造:
如果你知道你的意思,请明确地表达出来。
这个回答主要讲解对象的创建,包括使用和不使用显式构造函数的情况,因为其他回答中没有涉及到这方面内容。
考虑下面这个没有显式构造函数的类:
class Foo
{
public:
Foo(int x) : m_x(x)
{
}
private:
int m_x;
};
类 Foo 的对象可以通过以下 2 种方式创建:
Foo bar1(10);
Foo bar2 = 20;
根据实现方式不同,使用第二种类Foo的实例化方法可能会令程序员感到困惑,而这也不是程序员所期望的。将explicit
关键字作为构造函数的前缀加入会在Foo bar2 = 20;
处生成编译器错误。
通常情况下,将单个参数的构造函数声明为explicit
是一个好习惯,除非你的实现特别禁止它。
还要注意,带有
的构造函数都可以用作单个参数构造函数。因此,你可能还想将这些构造函数也声明为explicit
。
当你创建一个函数对象时,你故意不希望将单个参数构造函数设置为explicit,例如在查看此答案中声明的'add_x'结构体。在这种情况下,像add_x add30 = 30;
这样创建对象可能是有意义的。
这里是关于显式构造函数的一篇很好的文章。
explicit
关键字将转换构造函数变为非转换构造函数,因此代码更少出错。
explicit
关键字可用于强制要求显式调用构造函数。class C {
public:
explicit C() =default;
};
int main() {
C c;
return 0;
}
explicit
关键字放在构造函数C()
之前,告诉编译器只允许显式调用此构造函数。
explicit
关键字也可用于用户自定义类型转换运算符:
class C{
public:
explicit inline operator bool() const {
return true;
}
};
int main() {
C c;
bool b = static_cast<bool>(c);
return 0;
}
在这里,explicit
关键字强制要求只有显式转换是有效的,因此在此情况下,bool b = c;
将是无效的转换。在这样的情况下,explicit
关键字可以帮助程序员避免隐含的、意外的转换。这种用法已经被标准化在C++11中。
C c();
并不是你想象的意思:它声明了一个名为c
的函数,该函数不带参数并返回C
类的一个实例。 - 6502explicit operator bool()
是 C++11 版本的安全布尔类型,可以在条件检查中隐式使用(只能在条件检查中使用,据我所知)。在您的第二个示例中,这行代码也可以在 main()
中使用:if (c) { std::cout << "'c' is valid." << std:: endl; }
。除此之外,它不能在没有显式转换的情况下使用。 - Justin Time - Reinstate MonicaC c();
的错误,并在编译器探索器中进行了尝试,发现将默认构造函数设置为 explicit
实际上并没有起到任何作用。即使已经进行了代码更正,这个答案仍然是错误的。 - JDługoszCpp Reference永远是有帮助的!!! 有关explicit specifier的详细信息可以在这里找到。你可能还需要查看implicit conversions和copy-initialization。
快速浏览
explicit specifier指定了构造函数或转换函数(自C++11以来)不允许隐式转换或复制初始化。
示例如下:
struct A
{
A(int) { } // converting constructor
A(int, int) { } // converting constructor (C++11)
operator bool() const { return true; }
};
struct B
{
explicit B(int) { }
explicit B(int, int) { }
explicit operator bool() const { return true; }
};
int main()
{
A a1 = 1; // OK: copy-initialization selects A::A(int)
A a2(2); // OK: direct-initialization selects A::A(int)
A a3 {4, 5}; // OK: direct-list-initialization selects A::A(int, int)
A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
A a5 = (A)1; // OK: explicit cast performs static_cast
if (a1) cout << "true" << endl; // OK: A::operator bool()
bool na1 = a1; // OK: copy-initialization selects A::operator bool()
bool na2 = static_cast<bool>(a1); // OK: static_cast performs direct-initialization
// B b1 = 1; // error: copy-initialization does not consider B::B(int)
B b2(2); // OK: direct-initialization selects B::B(int)
B b3 {4, 5}; // OK: direct-list-initialization selects B::B(int, int)
// B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
B b5 = (B)1; // OK: explicit cast performs static_cast
if (b5) cout << "true" << endl; // OK: B::operator bool()
// bool nb1 = b2; // error: copy-initialization does not consider B::operator bool()
bool nb2 = static_cast<bool>(b2); // OK: static_cast performs direct-initialization
}
explicit operator bool()
与if
是一个特殊情况。无法使用用户定义的Bool
、explicit operator Bool()
和名为If
的函数来复制它。 - curiousguy在编写代码时,将你的单参数构造函数制作为最佳编程实践(包括那些具有默认值arg2
,arg3
等的构造函数),正如先前所述。
另一个类的良好实践是将复制构造和赋值操作设为私有(即禁用它),除非你确实需要实现它。这样做可以避免在使用C++为您默认创建的方法时产生指针的副本。另一种方法是从boost::noncopyable
派生。
explicit
不再只能用于构造函数,还可以用于类型转换运算符。假设你有一个类BigInt
,它有一个将对象转换为int
的类型转换运算符,并且还有一个将对象显式转换为std::string
的类型转换运算符(出于某种原因)。你可以这样写int i = myBigInt;
,但在将对象显式转换为std::string
时,则需要进行显式转换(最好使用static_cast
)。 - chrisint x(5);
) - Eitan Myronstd::string s = static_cast<std::string>(myBigInt)
?如果可以的话,能否进一步解释一下你的第一个评论呢?非常感谢! - Milanbool
在这方面是特殊的。那些答案和搜索“显式转换运算符”将带领你找到更多关于这个特性的文章,并且比评论链更适合。 - chris