【问题标题】:What's the point in defaulting functions in C++11?在 C++11 中默认函数有什么意义?
【发布时间】:2010-10-23 20:41:16
【问题描述】:

C++11 增加了将任何special member functions 告诉编译器create a default implementation 的能力。虽然我可以看到删除函数的价值,但显式默认函数的价值在哪里?只需将其留空,编译器就会这样做。

我能看到的唯一一点是,只有在不存在其他构造函数时才会创建默认构造函数:

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

但这真的比你现在做的更好吗?

class eg {
public:
    eg(int i);
    eg() {}
};

或者我错过了一个用例?

【问题讨论】:

    标签: c++ c++11 defaulted-functions


    【解决方案1】:

    我怀疑能够默认生成复制构造函数实际上会很有用。我看不到默认生成默认构造函数的用途,因为正如您所说,您输入的实现会更短。

    【讨论】:

      【解决方案2】:

      对我来说,它的禁用特性很有用,对于我目前创建的大多数类,我禁用复制和赋值 - 如果有一个编译器可以识别的特性,而不是依赖于链接器错误,那就太好了.

      【讨论】:

      • 即使现在 (C++98) 通过从 non_copyable 类派生,您也可以让编译器检测错误(即使在类本身中使用时)并且不必等待链接时间。
      • 我不相信引入推导来解决这样的问题。
      【解决方案3】:

      默认的构造函数将有一个声明,该声明将受制于正常的访问规则。例如。您可以保护默认的复制构造函数。如果没有这些新声明,默认生成的成员是公共的。

      【讨论】:

      • [dig] 另外:定义某些特殊成员函数可防止编译器默认其他成员函数,但这可以通过 = default 重新启用。例如,如果您实现自定义复制构造函数,则不会生成默认的移动构造函数。如果足够的话,您可以显式默认它,而不是自己实现它。
      【解决方案4】:

      Stroustrup's website 的例子可能会帮助你理解这一点:

      默认和删除功能——默认控制

      “禁止”的常用成语 复制”现在可以表达 直接:

      class X {
        // ...
      
        X& operator=(const X&) = delete;    // Disallow copying
        X(const X&) = delete;
      };
      

      相反,我们也可以明确地说 我们想要默认的复制行为:

      class Y {
        // ...
        Y& operator=(const Y&) = default;   // default copy semantics
        Y(const Y&) = default;
      
      };
      

      明确默认是 显然是多余的,但 cmets 这种影响和(更糟)用户 显式定义复制操作 旨在给出默认行为的是 并不少见。留给 编译器实现默认 行为更简单,更不容易出错, 并且通常会导致更好的目标代码。 可以使用“默认”机制 对于任何具有默认值的函数。 “删除”机制可用于 任何功能。例如,我们可以 消除不希望的转换,例如 这个:

      struct Z {
        // ...
      
        Z(long long);     // can initialize with an long long
        Z(long) = delete; // but not anything less
      };
      

      【讨论】:

      • 我实际上是在阅读了 BS 的 C++0x FAQ 后提出了这个问题,他的“明显多余”的评论促使我质疑整个默认问题。
      • 嗯,你可以在任何东西上使用 =delete,但不能使用 =default。本来喜欢写“int main() = default; // 回到阅读 StackOverflow”
      【解决方案5】:

      除了更改生成函数的可访问性(私有/受保护)之外,您还可以将它们设为虚拟。

      struct S
      {
          virtual ~S();
          virtual S& operator=(const S&);
      };
      
      S::~S() = default;
      S& S::operator=(const S&) = default;
      

      默认功能可以修改以下几个方面:

      • 访问(不公开)
      • 虚拟
      • 显式(构造函数)
      • 异常规范
      • 参数的常量性

      但要这样做,必须在类之外定义函数(C++0x Final Committee Draft 中的 8.4.2/2)。

      劳伦斯·克劳尔的原始提案的一个版本是here

      感谢Roger Pate 的澄清和引用。

      【讨论】:

      • 为什么 gcc 4.5.0 会为您发布的代码发出以下错误:'virtual S::~S()' 声明的虚拟不能在类主体中默认
      • @bpw:因为 8.4.2/2(在 N3092 中检查)明确禁止此代码。
      • @Roger 感谢您的澄清(其他地方)。正如你所指出的,我已经在类之外定义了函数。
      • 在类体中定义它是否有一些歧义?您可以在 Class 的主体中说 virtual ~Class() {} 而不是 virtual ~Class() = default; 这似乎很奇怪。在任何情况下,这两段代码的行为有什么不同吗?
      • 这个限制不再适用于较新的草稿(对我来说是 N3376 和连续的 C++14 草稿)。所以你可以声明virtual ~Class() = default
      【解决方案6】:

      1) 隐式生成的析构函数当前不是虚拟的。因此,您需要定义它们以使它们虚拟化,在这种情况下它们效率不高。使用 =default,您将同时拥有虚拟和高效作为隐式生成的析构函数。

      2) 与隐式生成的相反,它们将具有访问说明符。

      3) 如果你内联你的默认构造函数,你的类仍然是微不足道的。

      Here is an article elaborating this new feature.

      【讨论】:

        【解决方案7】:

        如果你有一个有很多属性的类,默认对复制构造函数更有用。 例如,如果你有这个类:

        class MyClass {
        private:
           int offset;
           std::string name;
           std::vector<Person*> relatives;
           float weight;
           MyClass* spouse;
           Vehicle* car;
           double houseArea;
           Date birth;
           Person* parents[2];
        
        public:
           /* Default constructor will be defined here */
        };
        

        不要以这种方式定义复制构造函数:

        MyClass(const MyClass& that) :
           offset(that.offset),
           name(that.name),
           relatives(that.relatives),
           weight(that.weight),
           spouse(that.spouse),
           car(that.car),
           houseArea(that.houseArea),
           birth(that.birth),
           parents(that.parents)
        {}
        

        你可以这样定义:

        MyClass(const MyClass&) = default;
        

        【讨论】:

        【解决方案8】:

        参见 Scott Meyer 的巨著“Effective Modern C++”中的第 17 项。它描述了生成(或不生成)默认复制构造函数、复制操作和移动操作的许多条件。

        换句话说,编译器可能不会“无论如何都做”。但是如果默认的特殊成员函数有意义,用户可以使用“default”关键字明确告诉编译器生成一个默认函数,否则不会生成。

        来自第 17 条末尾的要记住的事情:

        • 仅为缺少明确声明的移动操作、复制操作或析构函数的类生成移动操作。

        • 复制构造函数只为缺少显式声明的复制构造函数的类生成,如果声明了移动操作,它会被删除。复制赋值运算符仅针对缺少显式声明的复制赋值运算符的类生成,如果声明了移动操作,则将其删除。不推荐在具有显式声明的析构函数的类中生成复制操作。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-07-17
          • 1970-01-01
          • 2015-04-11
          • 2023-03-09
          • 2019-05-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多