简体   繁体   中英

Default construction of deleted constructor with braced initializer list

Let's say that I want to disable the construction of class, then I can do the following (as per Best style for deleting all constructors (or other function)? ):

// This results in Example being CopyConstructible:
struct Example {
  Example() = delete;
};

or

struct Example {
  template <typename... Ts>
  Example(Ts&&...) = delete;
};

or

struct Example {
  Example(const Example&) = delete;
};

where the first example can still be copied if constructed (which the intention is to disable), but the second two will disable almost all methods for creating Example . If I default construct any of the above using an empty braced initializer list, then the instance is successfully constructed. In Meyer's Effective Modern C++ he gives the example (bottom of page 51):

Widget w3{}; // calls Widget ctor with no args

which I would thus expect to fail for the above Example classes ie:

Example e{};

should not construct since it should call the deleted default constructor. However, it does, is usable, and if defined as the first case above, is also copyable. See live demo . My question is: Is this correct, and if so, why? Also, if this is correct, how do I completely disable the destruction of the class?

From ground up

We will first clarify what it means to initialize an object and how/when/if a constructor is invoked.
The following is my laymen's interpretation of the standard, for simplicity's sake some irrelevant details have been omitted or mangled.

Initializer

An initializer is one of the following

()     // parentheses
       // nothing
{}     // braced initializer list
= expr // assignment expression

The parentheses and braced initializer list may contain further expressions.
They are used like this, given struct S

new S()        // empty parentheses
S s(1, 2)      // parentheses with expression list as (1, 2)
S s            // nothing
S s{}          // empty braced initializer list
S s{{1}, {2}}  // braced initializer list with sublists
S s = 1        // assignment
S s = {1, 2}   // assignment with braced initializer list

Note that we have not yet mentioned constructors

Initialization

Initialization is performed according to what initializers are used.

new S()        // value-initialize
S s(1, 2)      // direct-initialize
S s            // default-initialize
S s{}          // list-initialize
S s{{1}, {2}}  // list-initialize
S s = 1        // copy-initialize
S s = {1, 2}   // list-initialize

Once initialization is performed, the object is considered initialized.

Note that, again, constructors have not been mentioned

List initialize

We will be primarily explaining what it means to list initialize something, as this is the question at hand.

When list initialization occurs, the following is considered in order

  1. If the object is an aggregate type and the list has a single element that is the object's type or is derived from the object's type, the object is initialized with that element
  2. If the object is an aggregate type, the object is aggregate initialized
  3. If the list is empty, and the object has a default constructor, the object is value-initialized (ends up calling default constructor)
  4. If the object is a class type, the constructors are considered, performing overload resolution with the elements of the list

Aggregate

An aggregate type is defined as [dcl.init.aggr]

An aggregate is an array or a class with
-- no user-provided, explicit, or inherited constructors
-- no private or protected non-static data members
-- no virtual functions, and no virtual, private, or protected base classes

Having a deleted constructor does not count towards providing a constructor.

Elements of an aggregate is defined as

The elements of an aggregate are:
-- for an array, the array elements in increasing subscript order, or
-- for a class, the direct base classes in declaration order, followed by the direct non-static data members that are not members of an anonymous union, in declaration order.

Aggregate-initialization is defined as

[...] the elements of the initializer list are taken as initializers for the elements of the aggregate, in order.

Example e{}

Following the rules above the question why Example e{} is legal is because

the initializer is a braced initializer list
uses list initialization
since Example is an aggregate type
uses aggregate initialization
and therefore does not invoke any constructor

When you write Example e{} , it is not default constructed. It is aggregate initialized. So, yes it is perfectly fine.

In fact, the following compiles

struct S
{
    S() = delete;
    S(const S&) = delete;
    S(S&&) = delete;
    S& operator=(const S&) = delete;
};

S s{};  //perfectly legal

Turn off construction

Make sure that Example is not an aggregate type to stop aggregate initialization and delete its constructors.

This is often trivial as most classes have private or protected data members. As such, it is often forgotten that aggregate initialization exists in C++.

The simplest way to make a class non-aggregate would be

struct S
{
    explicit S() = delete;
};
S s{};  //illegal, calls deleted default constructor

However, as of 2017 May 30, only gcc 6.1 and above and clang 4.0.0 will reject this, all versions of CL and icc will incorrectly accept this.

Other initializations

This is one of the craziest corners in C++, and it was hellish informative to look through the standard to understand what exactly happened. There have been lots of references already written and I will not attempt to explain them.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM