ctor:为什么使用'explicit'关键字可以防止赋值构造函数的创建?

5

我有一个类ByteArray,定义如下:

class ByteArray
{
public:
    explicit ByteArray( unsigned int uiSize = 0 );
    explicit ByteArray( const char * ucSource );
    ByteArray( const ByteArray & other );

    ByteArray & operator=( const char * ucSource );
    ByteArray & operator=( const ByteArray & other );
}

虽然几乎所有东西都能正常工作,但通过赋值构造ByteArray会导致编译错误。

ByteArray ba1( 5 );     // works
ByteArray ba2( ba1 );   // works
ByteArray ba3( "ABC" ); // works
ByteArray ba4;          // works
ba4 = "ABC";            // works
ByteArray ba5 = "ABC";  // <<<----- doesn't compile!

编译器出现了一个“无法将'const char *'转换为'ByteArray'”的错误提示。然而,“赋值构造函数”应该与复制构造函数相同,即ba5行应该像ba3行一样编译——与ba4及其后续赋值的构造方式不同。因此,我不太确定编译器遇到了什么问题。
我知道,解决方案是删除第3个构造函数前面的explicit。但我宁愿先理解发生了什么……
编辑:
回答说明得很好:ByteArray ba5 = "ABC";会被编译为ByteArray ba5( ByteArray("ABC") );——而不是像我以为的那样编译为ByteArray ba5("ABC");。显而易见,但有时你需要别人指出。谢谢大家的答案!
为什么要使用'explicit'呢?因为unsigned intconst char *之间存在歧义。如果我调用ByteArray ba(0);,那么两个构造函数都能处理它,所以我需要禁止隐式转换并使其成为explicit

2
你声明了构造函数为explicit,所以需要显式调用它。 - Mohit Jain
我使用显式声明来防止在资源受限环境中构建不必要的额外内容。 - stephen
5个回答

9

ByteArray ba5 = "ABC";是复制初始化,而不是赋值。

可以将其视为

ByteArray ba5(ByteArray("ABC"));

或者至少编译器是这么看的。在您的情况下,由于构造函数的explicit属性,这是不合法的 - 编译器想使用该转换构造函数执行副本初始化,但是它无法这样做,因为您没有显式使用它。


你的"把它看作是..."这句话正是我需要看到的,让我明白了。谢谢! - Robin

2

如果您不使用explicit关键字,编译器将允许使用“=”(复制初始化)将初始化转换为使用构造函数的初始化。但有时您不希望发生这种行为,因此使用explicit关键字来避免此转换。因此,您实际上获得了预期的结果。

一些可以通过显式构造函数进行限制的示例:

explicit T(const other_type &other);
T object = other;
f(other); // if f recieves object by value
return other;  // if function returns object by value
catch ( T other);
T array [ N ] = { other };

@LuchianGrigore 您的意思是f没有按值接收对象,还是f按值接收对象但复制被优化掉了? - Mohit Jain
1
不,我的意思是如果函数f通过引用参数或者返回引用的方式调用return other;。(或者评论最后是否暗示了一些?如果是这样,请忽略此部分) - Luchian Grigore
@LuchianGrigore 这就是我添加注释来澄清我的意图,即argumentreturn值确实是按值传递的原因。我会编辑它以使其更清晰。 - Mohit Jain

2

C++11 12.3.1/2 "构造函数的转换"说:

显式构造函数像非显式构造函数一样构造对象,但只有在使用直接初始化语法(8.5)或明确使用强制类型转换(5.2.9、5.4)时才这样做。

形式为:

ByteArray ba5 = "ABC";

根据8.5/14,这是复制初始化而不是直接初始化(ByteArray ba3( "ABC" ))(根据8.5/15),因此不能使用显式构造函数。


1
C++有两种初始化方式,即复制初始化和直接初始化。在复制初始化的情况下,正式规则要求使用复制构造函数;如果表达式的类型不正确,则会进行转换。(编译器可以省略额外的拷贝,但仍必须确保代码在省略时合法。)由=符号表示的初始化(在此情况下不是赋值运算符)使用复制初始化,如传递参数或返回值,抛出或捕获异常。在括号或花括号中标记的初始化(在C++11中),以及new表达式、基类和成员初始化以及各种显式转换(static_cast等)都是直接初始化。
当实际赋值时,规则当然是函数调用(这就是它所做的——不会构造新变量)。在您的情况下,ba4 = "ABC"有效,因为您具有接受char const*的赋值运算符;不需要隐式转换。
(顺便说一下:我会避免在复制构造函数中使用explicit。我不确定它确切的含义,也不确定其他人是否了解——explicit的目的是防止构造函数在隐式转换中被使用,当然,复制构造函数无论如何都不能用于任何转换。)

0
到目前为止提供的答案揭示了代码不起作用的原因。但这并不能解释为什么它不应该工作。
复制初始化是一种初始化变量的方式。explicit 是一种确保在构造或赋值时不发生隐式转换的方法。根据我的经验,复制初始化始终与直接初始化表现相同(尽管我不知道这是否得到保证)。 为什么“explicit”的存在会阻止复制初始化有效?或者,从另一个角度来看,我如何使用显式构造函数使复制初始化起作用?如果不可能,那么这肯定是语言规范中的缺陷——只要相关函数被声明和定义,启用它似乎没有任何劣势。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接