删除默认构造函数。对象仍然可以创建...有时 [英] Deleted default constructor. Objects can still be created... sometimes

查看:163
本文介绍了删除默认构造函数。对象仍然可以创建...有时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我认为由于C ++ 11用户定义的类型对象应该使用新的 {...} 语法而不是旧的(...)语法除了 std :: initializer_list 和类似参数的重载构造函数(例如 std :: vector :size ctor vs 1 elem init_list ctor))。



好处是:没有狭窄的隐式转换,没有问题的最烦琐的解析,一致性(?)。我看到没有问题,因为我认为他们是一样的(除了给出的例子)。



但他们不是。



纯疯狂的故事



{} 调用默认构造函数。



...除非:




  • 默认构造函数已删除,

  • 没有定义其他构造函数。



如果对象已经删除了默认构造函数,则 {} 可以创建一个对象。



...除非以下情况:




  • 对象具有已删除的默认构造函数,并且已定义

  • 其他构造函数。



然后它失败,并且调用已删除的构造函数。



...除非: p>


  • 对象具有已删除的构造函数,

  • 没有定义其他构造函数,

  • 至少是一个非静态数据成员。




$ b

但是你可以使用 {value} 来构造对象。





...除非:




  • 该类具有已删除的构造函数

  • ,并且至少一个数据成员默认已初始化。



然后也不会 {} {value} 可以创建一个对象。



我相信我错过了几个。讽刺的是,它被称为 uniform 初始化语法。我再次说明: 初始化语法。



这个疯狂是什么?



方案A



删除默认构造函数:



  struct foo {
foo()= delete;
};

//所有波纹管OK(没有错误,没有警告)
foo f = foo {};
foo f = {};
foo f {}; //将只使用从现在开始。



方案B



删除默认构造函数,其他构造函数已删除



  struct foo {
foo
foo(int)= delete;
};

foo f {}; // OK



方案C



默认构造函数,定义的其他构造函数



  struct foo {
foo()
foo(int){}
};

foo f {}; //错误调用删除的构造函数



场景D



已删除的默认构造函数,没有其他构造函数定义,数据成员



  struct foo {
int a;
foo()= delete;
};

foo f {}; //错误使用已删除的函数foo :: foo()
foo f {3}; // OK



方案E



默认构造函数,删除的T构造函数,T数据成员



  struct foo {
int a;
foo()= delete;
foo(int)= delete;
};

foo f {}; //错误:缺少初始化程序
foo f {3}; // OK



方案F



默认构造函数,类内数据成员初始化器



  struct foo {
int a = 3;
foo()= delete;
};

/ * Fa * / foo f {}; //错误:使用被删除的函数`foo :: foo()`
/ * Fb * / foo f {3}; //错误:没有匹配的函数调用`foo :: foo(init list)`



h1>

解决方案

当以这种方式查看内容时,很容易说,对象初始化的方式完全混乱。 >

最大的区别来自 foo 的类型:如果它是一个聚合类型。


它是一个聚合,如果它有:




  • 没有用户提供

  • 没有私人或受保护的非静态数据成员,

  • 没有大括号

  • 没有基类,
  • $ b $ b
  • 没有虚拟会员功能。




  • 在情境中ABDE: foo 是场景中的

  • foo 不是一个总计

  • 场景F:


    • 在c ++ 11中它不是一个聚合。

    • 在c ++ 14中是一个聚合。

    • g ++没有实现仍然把它作为非聚合甚至在C ++ 14。


      • 4.9 不会实现此功能。

      • code> 5.2.0

      • 回归)





b $ b

类型T的对象的列表初始化的效果是:




  • ...

  • 如果T是聚合类型,则执行聚合初始化。这需要考虑场景ABDE(和C ++ 14中的F)

  • 否则T的构造函数分为两个阶段:


    • 所有使用std :: initializer_list ...的构造函数

    • 否则[...] T的所有构造函数都参与重载解析[...] C(and C in C ++ 11)


  • ...



汇总T类型对象的初始化ABDE(F c ++ 14)):




  • 每个非静态类成员在类定义中出现,从
    初始化器列表的相应子句初始化。 (数组引用略)







< TL; DR



所有这些规则仍然看起来很复杂,并且引起头痛。我个人为自己过度简化这个(如果我这样拍自己在脚,那么就这样:我想我会在医院花两天,而不是几十天的头痛):




  • 对于汇总,每个数据成员都从列表初始化器的元素初始化

  • else call constructor







这不是违背了删除构造函数的全部目的吗? / p>

好吧,我不知道,但解决方案是让 foo 不是聚合。最常见的形式,不添加任何开销,并且不改变对象的使用的语法是让它继承一个空的结构:

  struct dummy_t {}; 

struct foo:dummy_t {
foo()= delete;
};

foo f {}; // ERROR调用已删除的构造函数

在某些情况下),一个替代方法是删除析构函数(这将使对象在任何上下文中不可实例化):

  struct foo { 
〜foo()= delete;
};

foo f {}; // ERROR使用已删除的函数`foo ::〜foo()`





b $ b

此答案使用从以下来源收集的信息:





非常感谢 @MM 帮助纠正和改进了这篇文章。


The naive, optimistic and oh.. so wrong view of the c++11 uniform initialization syntax

I thought that since C++11 user-defined type objects should be constructed with the new {...} syntax instead of the old (...) syntax (except for constructor overloaded for std::initializer_list and similar parameters (e.g. std::vector: size ctor vs 1 elem init_list ctor)).

The benefits are: no narrow implicit conversions, no problem with the most vexing parse, consistency(?). I saw no problem as I thought they are the same (except the example given).

But they are not.

A tale of pure madness

The {} calls the default constructor.

... Except when:

  • the default constructor is deleted and
  • there are no other constructors defined.

Then it looks like it it rather value initializes the object?... Even if the object has deleted default constructor, the {} can create an object. Doesn't this beat the whole purpose of a deleted constructor?

...Except when:

  • the object has a deleted default constructor and
  • other constructor(s) defined.

Then it fails with call to deleted constructor.

...Except when:

  • the object has a deleted constructor and
  • no other constructor defined and
  • at least a non-static data member.

Then it fails with missing field initializers.

But then you can use {value} to construct the object.

Ok maybe this is the same as the first exception (value init the object)

...Except when:

  • the class has a deleted constructor
  • and at least one data members in-class default initialized.

Then nor {} nor {value} can create an object.

I am sure I missed a few. The irony is that it is called uniform initialization syntax. I say again: UNIFORM initialization syntax.

What is this madness?

Scenario A

Deleted default constructor:

struct foo {
  foo() = delete;
};

// All bellow OK (no errors, no warnings)
foo f = foo{};
foo f = {};
foo f{}; // will use only this from now on.

Scenario B

Deleted default constructor, other constructors deleted

struct foo {
  foo() = delete;
  foo(int) = delete;
};

foo f{}; // OK

Scenario C

Deleted default constructor, other constructors defined

struct foo {
  foo() = delete;
  foo(int) {};
};

foo f{}; // error call to deleted constructor

Scenario D

Deleted default constructor, no other constructors defined, data member

struct foo {
  int a;
  foo() = delete;
};

foo f{}; // error use of deleted function foo::foo()
foo f{3}; // OK

Scenario E

Deleted default constructor, deleted T constructor, T data member

struct foo {
  int a;
  foo() = delete;
  foo(int) = delete;
};

foo f{}; // ERROR: missing initializer
foo f{3}; // OK

Scenario F

Deleted default constructor, in-class data member initializers

struct foo {
  int a = 3;
  foo() = delete;
};

/* Fa */ foo f{}; // ERROR: use of deleted function `foo::foo()`
/* Fb */ foo f{3}; // ERROR: no matching function to call `foo::foo(init list)`

解决方案

When viewing things this way it is easy to say there is complete and utter chaos in the way an object is initialized.

The big difference comes from the type of foo: if it is an aggregate type or not.

It is an aggregate if it has:

  • no user-provided constructors (a deleted or defaulted function does not count as user-provided),
  • no private or protected non-static data members,
  • no brace-or-equal-initializers for non-static data members (since c++11 until (reverted in) c++14)
  • no base classes,
  • no virtual member functions.

So:

  • in scenarios A B D E: foo is an aggregate
  • in scenarios C: foo is not an aggregate
  • scenario F:
    • in c++11 it is not an aggregate.
    • in c++14 it is an aggregate.
    • g++ hasn't implemented this and still treats it as a non-aggregate even in C++14.
      • 4.9 doesn't implement this.
      • 5.2.0 does
      • 5.2.1 ubuntu doesn't (maybe a regression)

The effects of list initialization of an object of type T are:

  • ...
  • If T is an aggregate type, aggregate initialization is performed. This takes care of scenarios A B D E (and F in C++14)
  • Otherwise the constructors of T are considered in two phases:
    • All constructors that take std::initializer_list ...
    • otherwise [...] all constructors of T participate in overload resolution [...] This takes care of C (and F in C++11)
  • ...

:

Aggregate initialization of an object of type T (scenarios A B D E (F c++14)):

  • Each non-static class member, in order appearance in the class definition, is copy-initialized from the corresponding clause of the initializer list. (array reference omitted)


TL;DR

All these rules can still seem very complicated and headache inducing. I personally over-simplify this for myself (if I thereby shoot myself in the foot then so be it: I guess I will spend 2 days in the hospital rather than having a couple of dozen days of headaches):

  • for an aggregate each data member is initialized from the elements of the list initializer
  • else call constructor

Doesn't this beat the whole purpose of a deleted constructor?

Well, I don't know about that, but the solution is to make foo not an aggregate. The most general form that adds no overhead and doesn't change the used syntax of the object is to make it inherit from an empty struct:

struct dummy_t {};

struct foo : dummy_t {
  foo() = delete;
};

foo f{}; // ERROR call to deleted constructor

In some situations (no non-static members at all, I guess), an alternate would be to delete the destructor (this will make the object not instantiable in any context):

struct foo {
  ~foo() = delete;
};

foo f{}; // ERROR use of deleted function `foo::~foo()`


This answer uses information gathered from:

Many thanks to @M.M who helped correct and improve this post.

这篇关于删除默认构造函数。对象仍然可以创建...有时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆