Purpose of Explicit Default Constructors

后端 未结 2 1907
无人共我
无人共我 2020-12-04 22:04

I recently noticed a class in C++0x that calls for an explicit default constructor. However, I\'m failing to come up with a scenario in which a default constructor can be c

相关标签:
2条回答
  • 2020-12-04 22:29

    Unless explicitly stated otherwise, all standard references below refers to N4659: March 2017 post-Kona working draft/C++17 DIS.


    (This answer focus specifically on explicit default constructors which have no parameters)


    Case #1 [C++11 through C++20]: Empty {} copy-list-initialization for non-aggregates prohibits use of explicit default constructors

    As governed by [over.match.list]/1 [emphasis mine]:

    When objects of non-aggregate class type T are list-initialized such that [dcl.init.list] specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:

    • (1.1) Initially, the candidate functions are the initializer-list constructors ([dcl.init.list]) of the class T and the argument list consists of the initializer list as a single argument.
    • (1.2) If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

    If the initializer list has no elements and T has a default constructor, the first phase is omitted. In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed. [ Note: This differs from other situations ([over.match.ctor], [over.match.copy]), where only converting constructors are considered for copy-initialization. This restriction only applies if this initialization is part of the final result of overload resolution.  — end note ]

    copy-list-initialization with an empty braced-init-list {} for non-aggregates prohibits use of explicit default constructors; e.g.:

    struct Foo {
        virtual void notAnAggregate() const {};
        explicit Foo() {}
    };
    
    void foo(Foo) {}
    
    int main() {
        Foo f1{};    // OK: direct-list-initialization
    
        // Error: converting to 'Foo' from initializer
        // list would use explicit constructor 'Foo::Foo()'
        Foo f2 = {};
        foo({});
    }
    

    Albeit the standard quote above refers to C++17, this likewise applies for C++11, C++14 and C++20.


    Case #2 [C++17 only]: A class type with a user-declared constructor that is marked as explicit is not an aggregate

    [dcl.init.aggr]/1 added was updated some between C++14 and C++17, mainly by allowing an aggregate to derive publicly from a base class, with some restrictions, but also prohibiting explicit constructors for aggregates [emphasis mine]:

    An aggregate is an array or a class with

    • (1.1) no user-provided, explicit, or inherited constructors ([class.ctor]),
    • (1.2) no private or protected non-static data members (Clause [class.access]),
    • (1.3) no virtual functions, and
    • (1.4) no virtual, private, or protected base classes ([class.mi]).

    As of P1008R1 (Prohibit aggregates with user-declared constructors), which has been implemented for C++20, we may no longer ever declare constructors for aggregates. In C++17 alone, however, we had the peculiar rule that whether a user-declared (but not user-provided) constructor was marked explicit decided whether the class type was an aggregate or not. E.g. the class types

    struct Foo {
        Foo() = default;
    };
    
    struct Bar {
        explicit Bar() = default;
    };
    

    were aggregates/not aggregates in C++11 through C++20 as follows:

    • C++11: Foo & Bar are both aggregates
    • C++14: Foo & Bar are both aggregates
    • C++17: Only Foo is an aggregate (Bar has an explicit constructor)
    • C++20: None of Foo or Bar are aggregates (both has user-declared constructors)
    0 讨论(0)
  • 2020-12-04 22:33

    This declares an explicit default constructor:

    struct A {
      explicit A(int a1 = 0);
    };
    
    A a = 0; /* not allowed */
    A b; /* allowed */
    A c(0); /* allowed */
    

    In case there is no parameter, like in the following example, the explicit is redundant.

    struct A {
      /* explicit is redundant. */
      explicit A();
    };
    

    In some C++0x draft (I believe it was n3035), it made a difference in the following way:

    A a = {}; /* error! */
    A b{}; /* alright */
    
    void function(A a);
    void f() { function({}); /* error! */ }
    

    But in the FCD, they changed this (though, I suspect that they didn't have this particular reason in mind) in that all three cases value-initialize the respective object. Value-initialization doesn't do the overload-resolution dance and thus won't fail on explicit constructors.

    0 讨论(0)
提交回复
热议问题