= 在函数声明后面的含义是删除。

338
class my_class
{
    ...
    my_class(my_class const &) = delete;
    ...
};

在这个上下文中,= delete 是什么意思?

除了 = 0= delete 之外,还有其他的“修饰符”吗?


1
我承认自己错了,我忽略了这个 C++0x 特性。我以为它是像 Qt 那样被解析为 0 的 #define,然后声明一个隐藏的函数什么的。 - Blindy
我记得有一个“disable”关键字,意思相同或类似。我是在想象吗?还是它们之间有微妙的区别? - Stewart
10个回答

270

删除一个函数是C++11的特性:

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

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

“delete”机制可以用于任何函数。例如,我们可以使用它来消除不需要的转换,像这样:

struct Z {
    // ...

    Z(long long);     // can initialize with a long long      
    Z(long) = delete; // but not anything less
};

7
传统的“禁止复制”的方法不就是将复制构造函数和赋值运算符声明为“私有”吗?这种方法会进一步指示编译器甚至不要生成这些函数。如果它们既是私有的又被声明为=delete,那么复制操作就被双重禁止了吗? - Reb.Cabin
24
@Reb,“=delete”使该方法即使在可以查看“private”方法的上下文中(即在类及其友元内部)也无法访问。 这消除了阅读代码时的任何不确定性。@Prasoon,第二个示例仍仅删除构造函数-很好看到删除“operator long()”的示例。 - Toby Speight
7
在使用禁止函数时,使用 = delete比使用private或其他类似机制更好,因为通常你希望这个被禁止的函数可以公开声明并且被考虑用于重载决议等,以便尽早失败并向用户提供最清晰的错误。任何涉及“隐藏”声明的解决方案都会降低此效果。 - Alex Celeste
2
有没有特殊的原因使拷贝构造函数公共并应用delete关键字。为什么不将构造函数保留为私有并应用该关键字? - Dohn Joe
并非总是如此。您无法在派生类中删除基类虚函数。 - The Philomath

115
  1. = 0 表示一个函数是纯虚函数,你无法从这个类中实例化对象。你需要继承它并实现这个方法。
  2. = delete 表示编译器不会为你生成那些构造函数。据我所知,这只允许在复制构造函数和赋值操作符上使用。但我对即将到来的标准不是太了解。

8
=delete 语法还有其他用途。例如,您可以使用它来明确禁止某些可能发生的隐式转换。为此,只需删除重载函数即可。更多信息,请查看 C++0x 的维基百科页面。 - LiKao
我一找到就会做。猜想是时候跟上C++0X了。 - mkaes
1
是啊,C++0x 真是太棒了。我迫不及待地想要 GCC 4.5+ 更加普及,这样我就可以开始使用 lambda 了。 - LiKao
14
“= delete”的描述并不完全正确。实际上,“= delete”可以用于任何函数,这样可以明确标记该函数已被删除,任何使用该函数的操作都会导致编译错误。对于特殊成员函数来说,这也意味着它们不会由编译器自动生成,但这只是因为它们被标记为已删除,而不是“= delete”本身的含义。 - MicroVirus
@MicroVirus 我想你的最后一点实际上是程序员声明了特殊函数的结果 - 将它们声明为已删除只是其中的一个例子。 - Stewart

55

这段摘自The C++ Programming Language [4th Edition] - Bjarne Stroustrup一书,讨论了使用=delete真正目的

3.3.4 禁用操作

在层次结构中对于一个类使用默认的复制或移动操作通常是一个灾难:仅给出一个基类指针,我们就无法知道派生类具有哪些成员,因此我们不知道如何复制它们。所以,通常最好的做法是删除默认的复制和移动操作,也就是消除那两个操作的默认定义:

class Shape {
public:
  Shape(const Shape&) =delete; // no copy operations
  Shape& operator=(const Shape&) =delete;

  Shape(Shape&&) =delete; // no move operations
  Shape& operator=(Shape&&) =delete;
  ˜Shape();
    // ...
};

现在,尝试复制形状将被编译器捕获。

=delete 机制是通用的,即它可以用于抑制任何操作。


20

10
我曾经遵循的编码标准通常在大多数类声明中都有以下内容。
//  coding standard: disallow when not used
T(void)                  = delete; // default ctor    (1)
~T(void)                 = delete; // default dtor    (2)
T(const T&)              = delete; // copy ctor       (3)
T(const T&&)             = delete; // move ctor       (4)
T& operator= (const T&)  = delete; // copy assignment (5)
T& operator= (const T&&) = delete; // move assignment (6)

如果您使用这6个之中的任何一个,只需注释掉相应的行即可。

例如:class FizzBus仅需要dtor,因此不使用其他5个。

//  coding standard: disallow when not used
FizzBuzz(void)                         = delete; // default ctor (1)
// ~FizzBuzz(void);                              // dtor         (2)
FizzBuzz(const FizzBuzz&)              = delete; // copy ctor    (3)
FizzBuzz& operator= (const FizzBuzz&)  = delete; // copy assig   (4)
FizzBuzz(const FizzBuzz&&)             = delete; // move ctor    (5)
FizzBuzz& operator= (const FizzBuzz&&) = delete; // move assign  (6)

我们只在这里注释掉了其中的1个,并在其他地方安装了它的实现(可能是符合编码标准的地方)。另外5个(共6个)使用delete被禁止。
你也可以使用“= delete”来禁止不同大小值的隐式转换...例如。
// disallow implicit promotions 
template <class T> operator T(void)              = delete;
template <class T> Vuint64& operator=  (const T) = delete;
template <class T> Vuint64& operator|= (const T) = delete;
template <class T> Vuint64& operator&= (const T) = delete;

使用已删除的构造函数创建类对象是非法的。 - KeyC0de
@Nikos - 不需要 -- 你只需要提供一个构造函数。在添加“T() = delete;”的示例中,阻止编译器添加(最小化的)默认构造函数有时很有用,但仍然允许你添加(假定执行某些有用操作的)构造函数。 - 2785528

5

已删除的函数隐式成为内联函数

(现有答案的补充说明)

...而且已删除的函数必须是该函数的第一个声明(除了删除函数模板显式特化 - 删除应在特化的第一个声明处),这意味着您不能声明一个函数,然后在其定义局部于翻译单元时再将其删除。

引用[dcl.fct.def.delete]/4

A deleted function is implicitly inline. ( Note: The one-definition rule ([basic.def.odr]) applies to deleted definitions. — end note ] A deleted definition of a function shall be the first declaration of the function or, for an explicit specialization of a function template, the first declaration of that specialization. [ Example:

struct sometype {
  sometype();
};
sometype::sometype() = delete;      // ill-formed; not first declaration

end example )

一个带有已删除定义的主函数模板可以被专门化

虽然通常规则是避免专门化函数模板,因为特化不参与重载解析的第一步,但在某些情况下它可能会很有用。例如,使用一个没有定义的非重载主函数模板来匹配所有类型,其中一个不想将其隐式转换为其他匹配转换重载的类型;即通过只在未定义、非重载主函数模板的显式专门化中实现精确类型匹配来隐式地去除多个隐式转换匹配。

在C++11之前的删除函数概念中,可以通过简单地省略主函数模板的定义来实现这一点,但这会导致模糊的未定义引用错误,这些错误无疑完全没有作者主函数模板的语义意图(有意省略?)。如果我们改为明确删除主函数模板,则在找不到适当的显式专门化时的错误消息变得更加友好,并且还表明主函数模板的省略/删除是有意的。

#include <iostream>
#include <string>

template< typename T >
void use_only_explicit_specializations(T t);

template<>
void use_only_explicit_specializations<int>(int t) {
    std::cout << "int: " << t;
}

int main()
{
    const int num = 42;
    const std::string str = "foo";
    use_only_explicit_specializations(num);  // int: 42
    //use_only_explicit_specializations(str); // undefined reference to `void use_only_explicit_specializations< ...
}

然而,与其简单地省略上述主函数模板的定义,在没有明确的特化匹配时产生一个晦涩的未定义引用错误,不如删除主模板定义:

#include <iostream>
#include <string>

template< typename T >
void use_only_explicit_specializations(T t) = delete;

template<>
void use_only_explicit_specializations<int>(int t) {
    std::cout << "int: " << t;
}

int main()
{
    const int num = 42;
    const std::string str = "foo";
    use_only_explicit_specializations(num);  // int: 42
    use_only_explicit_specializations(str);
    /* error: call to deleted function 'use_only_explicit_specializations' 
       note: candidate function [with T = std::__1::basic_string<char>] has 
       been explicitly deleted
       void use_only_explicit_specializations(T t) = delete; */
}

提供更易读的错误信息,其中删除意图也清晰可见(其中未定义的引用错误可能导致开发人员认为这是一个不周到的错误)。

回到为什么我们要使用这个技术?同样,显式特化可以有助于隐式地消除隐式转换。

#include <cstdint>
#include <iostream>

void warning_at_best(int8_t num) { 
    std::cout << "I better use -Werror and -pedantic... " << +num << "\n";
}

template< typename T >
void only_for_signed(T t) = delete;

template<>
void only_for_signed<int8_t>(int8_t t) {
    std::cout << "UB safe! 1 byte, " << +t << "\n";
}

template<>
void only_for_signed<int16_t>(int16_t t) {
    std::cout << "UB safe! 2 bytes, " << +t << "\n";
}

int main()
{
    const int8_t a = 42;
    const uint8_t b = 255U;
    const int16_t c = 255;
    const float d = 200.F;

    warning_at_best(a); // 42
    warning_at_best(b); // implementation-defined behaviour, no diagnostic required
    warning_at_best(c); // narrowing, -Wconstant-conversion warning
    warning_at_best(d); // undefined behaviour!

    only_for_signed(a);
    only_for_signed(c);

    //only_for_signed(b);  
    /* error: call to deleted function 'only_for_signed' 
       note: candidate function [with T = unsigned char] 
             has been explicitly deleted
       void only_for_signed(T t) = delete; */

    //only_for_signed(d);
    /* error: call to deleted function 'only_for_signed' 
       note: candidate function [with T = float] 
             has been explicitly deleted
       void only_for_signed(T t) = delete; */
}

3
= delete 是C++11引入的一个特性。根据=delete,将不允许调用该函数。
具体来说,在一个类中,如果使用=delete声明了某个成员函数,则该函数将无法被调用。
Class ABC{
 Int d;
 Public:
  ABC& operator= (const ABC& obj) =delete
  {

  }
};

在调用此函数进行对象分配时,不允许这样做。这意味着赋值运算符将限制从一个对象到另一个对象的复制。


2
新的C++0x标准。请参阅N3242工作草案中的第8.4.3节。 (链接)

2

一个小例子来总结一些常见用法:

class MyClass
{
public:
    // Delete copy constructor:
    // delete the copy constructor so you cannot copy-construct an object
    // of this class from a different object of this class
    MyClass(const MyClass&) = delete;

    // Delete assignment operator:
    // delete the `=` operator (`operator=()` class method) to disable copying
    // an object of this class
    MyClass& operator=(const MyClass&) = delete;

    // Delete constructor with certain types you'd like to
    // disallow:
    // (Arbitrary example) don't allow constructing from an `int` type. Expect
    // `uint64_t` instead.
    MyClass(uint64_t);
    MyClass(int) = delete;

    // "Pure virtual" function:
    // `= 0` makes this is a "pure virtual" method which *must* be overridden 
    // by a child class
    uint32_t getVal() = 0;
}

待办事项:

  1. 我仍需要制作一个更详尽的示例,并运行它以展示一些用法和输出,以及它们对应的错误消息。

另请参阅

  1. https://www.stroustrup.com/C++11FAQ.html#default - "默认控制: defaultdelete" 章节

0

这是C++ 0x标准中的新功能,您可以删除继承的函数。


16
您可以删除任何函数。例如,void foo(int); template <class T> void foo(T) = delete; 可以停止所有隐式转换。只有 int 类型的参数才会被接受,其他所有类型的参数都将尝试实例化一个“已删除”的函数。 - UncleBens
= delete 也适用于非成员函数。这对于防止隐式类型转换非常有用。 - A Fog

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接