什么是默认方法以及如何正确使用它们?

时间:2012-12-01 00:41:05

标签: c++ c++11

  

可能重复:
  What’s the point in defaulting functions in C++11?

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

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

3 个答案:

答案 0 :(得分:4)

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

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

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

问题是编译器只会在某些条件下提供定义。也就是说,有些条件下不会提供定义。

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

struct NoDefault
{
  NoDefault(float f);
};

NoDefault不能默认构建。因此,它不能用于需要默认构造的任何环境中。您无法NoDefault()创建临时。你不能create arrays of NoDefault(因为那些是默认构造的)。您无法创建std::vector<NoDefault>call the sizing constructor without providing a value to copy from,也无法创建要求类型为DefaultConstructible的任何其他操作。

但是,你可以这样做:

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

这会解决所有问题,对吧?

<强> WRONG!

这与此不一样:

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

为什么呢?因为StdDefault普通类型。那是什么意思?我不会解释整个事情,但go here for the details.制作类型是微不足道的,通常是一个有用的功能,当你可以做到。

普通类型的一个要求是它没有用户提供的默认构造函数。 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;
};

答案 1 :(得分:3)

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

8.4.2明确默认的函数[dcl.fct.def.default]

  

1表单的函数定义:   attribute-specifier-seqopt decl-specifier-seqopt declarator = default ;   被称为明确默认的定义。明确默认的功能应为    - 成为特殊会员职能,
   - 具有相同的声明函数类型(可能不同的ref限定符除外)   在复制构造函数或复制赋值运算符的情况下,参数类型可以是“引用”   非const 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,您只需查看标题即可获得保证。

答案 2 :(得分:3)

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

struct S {

};

int main() {

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

}

编译器将生成复制构造函数和默认构造函数,从而允许我们像我们一样实例化S对象(1)并将s2复制到s1( 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
};

我们还能够显式定义赋值运算符(就像默认的/ copy-constructor一样,当我们不自己编写时,会为我们生成一个默认值)。举个例子来说:

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

}

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