防止在构造函数中进行不希望的转换

Posted

技术标签:

【中文标题】防止在构造函数中进行不希望的转换【英文标题】:Prevent undesired conversion in constructor 【发布时间】:2016-08-11 20:17:44 【问题描述】:

根据here,explicit

指定构造函数和转换运算符 (C++11 起) 不允许隐式转换或复制初始化。

那么,这两种技术是否相同?

struct Z 
        // ...
        Z(long long);     // can initialize with a long long
        Z(long) = delete; // but not anything smaller
;

struct Z 
        // ...
        explicit Z(long long);     // can initialize ONLY with a long long
;

【问题讨论】:

【参考方案1】:

不,它们不一样。 explicit 如果选择了该构造函数,则不允许隐式转换为该类型 - 参数中的隐式转换无关紧要。 delete 如果选择了构造函数,则不允许任何构造,并且可用于禁止隐式 argument 转换。

例如:

struct X 
    explicit X(int )  
;

void foo(X )  

foo(4);      // error, because X's constructor is explicit
foo(X3);   // ok
foo(X'3'); // ok, this conversion is fine

这与delete构造函数不同:

struct Y 
    Y(int )  
    Y(char ) = delete;
;

void bar(Y )  

bar(4);      // ok, implicit conversion to Y since this constructor isn't explicit
bar('4');    // error, this constructor is deleted
bar(Y'4'); // error, doesn't matter that we're explicit

这两种技术也是正交的。如果你想让一个类型不能被隐式转换并且只能从一个int构造,你可以同时做:

struct W 
    explicit W(int )  

    template <class T>
    W(T ) = delete;
;

void quux(W );

quux(4);      // error, constructor is explicit
quux('4');    // error, constructor is deleted
quux(4L);     // error, constructor is deleted
quux(W'4'); // error, constructor is deleted
quux(W5);   // ok

【讨论】:

blech,不必要的使用统一初始化。使用直接初始化 plix。 @Puppy 你对我投了反对票,因为我用了牙套?认真的吗? 实际上,Stroustrup 也鼓励使用大括号初始值设定项(第 6.3.5 节,“C++ 编程语言 - 第 4 版。”,B.S.,2013)。 @Puppy:他们称之为统一初始化,因为它总是有效并且永远不会出错。你为什么要避免这种情况?【参考方案2】:

它们并不相同。

Z z = 1LL;

以上适用于非显式版本,但不适用于显式版本。

显式声明Z 的构造函数不会阻止构造函数参数从其他类型的转换。它可以防止在不显式调用构造函数的情况下将参数转换为Z

以下是显式构造函数调用的示例。

Z z = Z(1LL);

【讨论】:

注意,它调用显式构造函数,以及复制/移动构造函数。 Z z(1LL); 只会调用显式构造函数。【参考方案3】:

explicit 阻止隐式转换到您的类型

您的=delete 技术阻止了从longlong long 的隐式转换。

这些几乎是不相关的。

有4个案例可以说明区别:

Z z = 1L;
Z z = 1LL;

是从longlong longZ 的隐式转换。

Z z = Z(1L);
Z z = Z(1LL);

是从longlong longZ 的显式转换。

explicit Z(long long) 块:

Z z = 1L;
Z z = 1LL;

Z(long)=delete 块:

Z z = 1L;
Z z = Z(1L);

explicit Z(long long) 允许Z z = Z(1L),因为从longlong long 的转换是隐式的,但与之后发生的到Z 的显式转换无关。

请注意,explicit=delete 的混合使您的 4 个版本中只有 Z z=Z(1LL) 有效。

(以上假设是有效的复制或移动ctor;如果不是,请将Z z=Z(...) 替换为Z z(...) 并得出相同的结论)。

【讨论】:

【参考方案4】:
struct Zb 
        Zb(long long)
        ;     // can initialize with a long long
        Zb(long) = delete; // but not anything smaller
    ;

struct Za 
        // ...
        explicit Za(long long)
        ;     // can initialize ONLY with a long long
    ;

int main()

    Za((long long)10);  // works
    Za((long)10);       // works    

    Zb((long long)10);  // works
    Zb((long)10);       // does not work

    return 0;

您的示例需要显式删除。

直播:http://cpp.sh/4sqb

【讨论】:

【参考方案5】:

它们不一样。

来自标准工作草案n4296

12.3.1 - [class.conv.ctor]:1 声明的构造函数没有函数说明符显式指定从其类型的转换 其类的类型的参数。这样的构造函数称为转换构造函数

2 显式构造函数像非显式构造函数一样构造对象,但只有在 直接初始化语法 (8.5) 或显式使用强制类型转换 (5.2.9, 5.4)。默认构造函数 可能是显式构造函数;这样的构造函数将用于执行默认初始化或值初始化 (8.5)。

后面各有一个例子:

struct X 
    X(int);
    X(const char*, int =0);
    X(int, int);
;

void f(X arg) 
    X a = 1;        // a = X(1)
    X b = "Jessie"; // b = X("Jessie",0)
    a = 2;          // a = X(2)
    f(3);           // f(X(3))
    f(1, 2);      // f(X(1,2))

使用显式构造函数:

struct Z 
    explicit Z();
    explicit Z(int);
    explicit Z(int, int);
;

Z a;                      // OK: default-initialization performed
Z a1 = 1;                 // error: no implicit conversion
Z a3 = Z(1);              // OK: direct initialization syntax used
Z a2(1);                  // OK: direct initialization syntax used
Z* p = new Z(1);          // OK: direct initialization syntax used
Z a4 = (Z)1;              // OK: explicit cast used
Z a5 = static_cast<Z>(1); // OK: explicit cast used
Z a6 =  3, 4 ;          // error: no implicit conversion

【讨论】:

以上是关于防止在构造函数中进行不希望的转换的主要内容,如果未能解决你的问题,请参考以下文章

不存在合适的构造函数来将“test *”转换为“test”,构造函数,

mPDF 不使用构造函数参数

如何防止使用 TypeScript 在构造函数中未定义成员?

1. 导读

避免构造函数中的隐式转换。 'explicit' 关键字在这里没有帮助

借助模型构造函数,如何在 Spring Boot 中进行查询