首页 文章

对于默认构造函数和析构函数,“= default”与“{}”有什么不同?

提问于
浏览
127

我最初发布这个只是关于析构函数的问题,但现在我正在添加对默认构造函数的考虑 . 这是原始问题:

如果我想给我的类一个虚拟的析构函数,但是与编译器生成的析构函数相同,我可以使用= default:class Widget {
上市:
virtual~Widget()=默认值;
};
但似乎我可以通过使用空定义减少键入来获得相同的效果:class Widget {
上市:
virtual~Widget(){}
};
这两种定义的行为方式有何不同?

根据此问题的回复,默认构造函数的情况似乎相似 . 鉴于析构函数“ =default " and " {} ”之间的含义几乎没有差异,默认构造函数的这些选项之间的含义几乎没有差异吗?也就是说,假设我想创建一个类型,其中该类型的对象将被创建和销毁,为什么我要说

Widget() = default;

代替

Widget() {}

如果在原始帖子违反某些SO规则后扩展此问题,我深表歉意 . 为默认构造函数发布一个几乎完全相同的问题让我觉得不太理想 .

3 回答

  • 74

    在询问构造函数而不是析构函数时,这是一个完全不同的问题 .

    如果你的析构函数是 virtual ,则差异可以忽略不计,as Howard pointed out . 但是,如果你的析构函数是非虚拟的,那么这是一个完全不同的故事 . 构造函数也是如此 .

    对特殊成员函数使用 = default 语法(默认构造函数,复制/移动构造函数/赋值,析构函数等)意味着与简单地执行 {} 非常不同 . 对于后者,函数变为"user-provided" . 这改变了一切 .

    根据C 11的定义,这是一个微不足道的类:

    struct Trivial
    {
      int foo;
    };
    

    如果您尝试默认构造一个,编译器将自动生成默认构造函数 . 复制/移动和破坏也是如此 . 因为用户没有提供任何这些成员函数,所以C 11规范认为这是一个“普通”类 . 因此,这样做是合法的,比如记忆它们的内容以初始化它们等等 .

    这个:

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

    顾名思义,这不再是微不足道的 . 它有一个用户提供的默认构造函数 . 如果它是空的并不重要;就C 11的规则而言,这不是一个微不足道的类型 .

    这个:

    struct Trivial2
    {
      int foo;
    
      Trivial2() = default;
    };
    

    顾名思义,这是一个微不足道的类型 . 为什么?因为你告诉编译器自动生成默认构造函数 . 因此,构造函数不是“用户提供的” . 因此,类型计算为微不足道,因为它没有用户提供的默认构造函数 .

    当您添加阻止创建此类函数的成员函数时, = default 语法主要用于复制构造函数/赋值等操作 . 但它也会触发编译器的特殊行为,因此它在默认构造函数/析构函数中也很有用 .

  • 37

    之间的重要区别

    class B {
        public:
        B(){}
        int i;
        int j;
    };
    

    class B {
        public:
        B() = default;
        int i;
        int j;
    };
    

    B() = default; 定义的默认构造函数被认为是 not-user defined . 这意味着在值初始化的情况下,如

    B* pb = new B();  // use of () triggers value-initialization
    

    完全没有使用构造函数的特殊类型的初始化将发生,对于内置类型,这将导致零初始化 . 如果是 B(){} ,则不会发生这种情况 . C标准n3337 § 8.5 / 7说

    对类型为T的对象进行值初始化意味着: - 如果T是具有用户提供的构造函数(12.1)的(可能是cv限定的)类类型(第9节),则调用T的默认构造函数(并且如果T没有可访问的默认构造函数,则初始化是错误的); - 如果T是一个(可能是cv限定的)非联合类类型而没有用户提供的构造函数,那么该对象是零初始化的,如果T的隐式声明的默认构造函数是非平凡的,则调用该构造函数 . - 如果T是数组类型,则每个元素都是值初始化的; - 否则,对象被零初始化 .

    例如:

    #include <iostream>
    
    class A {
        public:
        A(){}
        int i;
        int j;
    };
    
    class B {
        public:
        B() = default;
        int i;
        int j;
    };
    
    int main()
    {
        for( int i = 0; i < 100; ++i) {
            A* pa = new A();
            B* pb = new B();
            std::cout << pa->i << "," << pa->j << std::endl;
            std::cout << pb->i << "," << pb->j << std::endl;
            delete pa;
            delete pb;
        }
      return 0;
    }
    

    可能的结果:

    0,0
    0,0
    145084416,0
    0,0
    145084432,0
    0,0
    145084416,0
    //...
    

    http://ideone.com/k8mBrd

  • 28

    它们都是非平凡的 .

    它们都具有相同的noexcept规范,具体取决于基础和成员的noexcept规范 .

    到目前为止我唯一的区别是,如果 Widget 包含一个基础或成员,其中包含一个不可访问或删除的析构函数:

    struct A
    {
    private:
        ~A();
    };
    
    class Widget {
        A a_;
    public:
    #if 1
       virtual ~Widget() = default;
    #else
       virtual ~Widget() {}
    #endif
    };
    

    然后 =default 解决方案将编译,但 Widget 将不是可破坏的类型 . 即如果你试图破坏一个 Widget ,你'll get a compile-time error. But if you don' t,你有一个工作程序 .

    Otoh,如果你提供用户提供的析构函数,那么无论你是否破坏了 Widget ,事情都无法编译:

    test.cpp:8:7: error: field of type 'A' has private destructor
        A a_;
          ^
    test.cpp:4:5: note: declared private here
        ~A();
        ^
    1 error generated.
    

相关问题