10

可能重复:
在 C++11 中默认函数有什么意义?

C++11 引入了默认方法(例如void myMethod() = default;)。

它对方法有什么作用(方法在被defaulted 后如何表现)。我如何正确使用它们(它的用途是什么)?

4

3 回答 3

4

有许多类成员被 C++ 标准视为“特殊成员函数”。这些都是:

  • 默认构造函数(可以不带参数调用的构造函数)。
  • 复制构造函数(可以使用一个参数调用的构造函数,该参数是对象类型作为左值引用)。
  • 复制赋值运算符(一个 operator= 重载,可以使用一个参数调用,该参数是对象类型作为左值引用或值)。
  • 移动构造函数(可以使用一个参数调用的构造函数,该参数是对象类型作为右值引用)。
  • 移动赋值运算符(一个 operator= 重载,可以使用一个参数调用,该参数是对象类型作为右值引用或值)。
  • 析构函数。

这些成员函数的特殊之处在于语言在类型上对它们做了特殊的事情。使它们与众不同的另一件事是,如果您不这样做,编译器可以为它们提供定义。这些是您可以使用= default;语法的唯一功能。

问题是编译器只会在特定条件下提供定义。也就是说,存在不提供定义的条件。

我不会遍历整个列表,但是其他人提到的一个例子。如果为不是特殊构造函数的类型提供构造函数(即:不是上面提到的构造函数之一),则不会自动生成默认构造函数。因此,这种类型:

struct NoDefault
{
  NoDefault(float f);
};

NoDefault不能默认构造。因此,它不能在需要默认构造的任何上下文中使用。你不能做NoDefault()一个临时的。您不能创建数组NoDefault(因为它们是默认构造的)。如果不提供要从中复制的值或任何其他要求类型为 的操作,则无法创建std::vector<NoDefault>并调用大小调整构造函数。DefaultConstructible

但是,您可以这样做:

struct UserDefault
{
  UserDefault() {}
  UserDefault(float f);
};

那会解决一切,对吧?

错误的!

这与以下内容不同:

struct StdDefault
{
  StdDefault() = default;
  StdDefault(float f);
};

为什么?因为StdDefault平凡的类型。这意味着什么?我不会解释整个事情,但请到这里了解详细信息。当您可以做到时,使类型变得微不足道通常是一个有用的功能。

普通类型的要求之一是它没有用户提供的默认构造函数。UserDefault有一个提供的,即使它与编译器生成的完全相同。因此,UserDefault并非小事。StdDefault是一个普通类型,因为= default语法意味着编译器会生成它。所以它不是用户提供的。

语法告诉编译器, “= default无论如何都要生成这个函数,即使你通常不会。” 这对于确保类是普通类型很重要,因为您实际上无法像编译器那样实现特殊成员。它允许您强制编译器生成函数,即使它不会。

这在许多情况下都非常有用。例如:

class UniqueThing
{
public:
  UniqueThing() : m_ptr(new SomeType()) {}
  UniqueThing(const UniqueThing &ptr) : m_ptr(new SomeType(*ptr)) {}
  UniqueThing &operator =(const UniqueThing &ptr)
  {
    m_ptr.reset(new SomeType(*ptr)); return *this;
  }

private:
  std::unique_ptr<SomeType> m_ptr;
};

我们必须编写这些函数中的每一个,以使类复制 unique_ptr 的内容;没有办法避免这种情况。但我们也希望它是可移动的,并且移动构造函数/赋值运算符不会自动为我们生成。重新实现它们是很愚蠢的(如果我们添加更多的成员则容易出错),所以我们可以使用以下= default语法:

class UniqueThing
{
public:
  UniqueThing() : m_ptr(new SomeType()) {}
  UniqueThing(const UniqueThing &ptr) : m_ptr(new SomeType(*ptr)) {}
  UniqueThing(UniqueThing &&ptr) = default;
  UniqueThing &operator =(const UniqueThing &ptr)
  {
    m_ptr.reset(new SomeType(*ptr)); return *this;
  }

  UniqueThing &operator =(UniqueThing &&ptr) = default;

private:
  std::unique_ptr<SomeType> m_ptr;
};
于 2012-12-01T02:38:11.677 回答
3

实际上,default只能适用于特殊方法——即构造函数、析构函数、赋值运算符:

8.4.2 显式默认函数 [dcl.fct.def.default]

1 形式为:的函数定义 attribute-specifier-seqopt decl-specifier-seqopt declarator = default ; 称为显式默认定义。一个显式默认的函数应该
——是一个特殊的成员函数
——具有相同的声明函数类型(除了可能不同的引用限定符以及在复制构造函数或复制赋值运算符的情况下,参数类型可以是“引用非常量 T”,其中 T 是成员函数的类的名称),就好像它已被隐式声明,并且
- 没有默认参数。

它们的行为与编译器生成它们的行为相同,并且对于以下情况很有用:

class Foo{
     Foo(int) {}
};

在这里,默认构造函数不存在,因此Foo f;无效。但是,您可以告诉编译器您需要一个默认构造函数,而无需自己实现它:

class Foo{
     Foo() = default;
     Foo(int) {}
};

编辑:@Nicol Bolas 在评论中指出,这确实不能解释为什么你更喜欢这个Foo() {}。我的猜测是:如果您在实现文件中分离了一个带有实现的类,那么您的类定义(如果您不使用=default)将如下所示:

class Foo{
     Foo();
     Foo(int);
};

假设的是,=default它旨在提供一种惯用的方式来告诉您“我们没有在构造函数中做任何特别的事情”。使用上面的类定义,默认构造函数很可能不对类成员进行值初始化。有了 ,=default您只需查看标题即可获得保证。

于 2012-12-01T00:49:11.473 回答
3

在构造函数或复制构造函数上使用= default意味着编译器将在编译时为您生成该函数。当您从class/中排除相应的功能时,通常会发生这种情况struct。在您定义自己的构造函数的情况下,这种新语法允许您显式地告诉编译器默认构造通常不会的构造函数。举个例子:

struct S {

};

int main() {

    S s1; // 1
    S s2(s1); // 2

}

编译器将生成一个复制构造函数和一个默认构造函数,从而允许我们S像 (1) 那样实例化一个对象并复制s2s1(2) 中。但是如果我们定义自己的构造函数,我们会发现我们无法以同样的方式实例化:

struct S {
    S(int);
};

int main() {

    S s;    // illegal
    S s(5); // legal

}

这失败了,因为S已经给定了一个自定义的构造函数,并且编译器不会生成默认构造函数。但是,如果我们仍然希望编译器给我们一个,我们可以使用以下方式明确传达这一点default

struct S {
    S(int);
    S() = default;
    S(const S &) = default;
};

int main() {

    S s;     // legal
    S s(5);  // legal

    S s2(s); // legal

}

但是请注意,只有具有特定函数签名的特定函数才能被重载。因此这将失败,因为它既不是复制构造函数,也不是默认构造函数,也不是移动或赋值运算符:

struct S {
    S(int) = default; // error
};

我们还可以显式定义赋值运算符(就像默认/复制构造函数一样,当我们不写出自己的构造函数时,会为我们生成一个默认值)。所以举个例子:

struct S {
    int operator=(int) {
        // do our own stuff
    }
};

int main() {

    S s;

    s = 4;

}

这可以编译,但在我们想要将另一个S对象分配到s. 这是因为我们自己编写的,编译器没有为我们提供一个。这就是default发挥作用的地方:

struct S {
    int operator=(int) {
        // do our own stuff
    }

    S & operator=(const S &) = default;
};

int main() {

    S s1, s2;

    s1 = s2; // legal

}

但是从技术上讲,当没有其他函数“覆盖”它时,将赋值运算符、复制或移动构造函数定义为默认值是多余的,因为无论如何编译器都会为您提供一个。它不会为您提供的唯一情况是您定义自己的(或其变体)。但作为一个偏好问题,如果有意为编译器省略,要求编译器使用上述新语法提供默认函数会更明确、更容易查看和理解。

于 2012-12-01T01:09:15.817 回答