为什么要显式删除构造函数而不是将其设为私有?

Why explicitly delete the constructor instead of making it private?

本文关键字:删除 构造函数 为什么      更新时间:2023-10-16

何时/为什么我要显式删除构造函数?假设原因是为了防止其使用,为什么不让它private呢?

class Foo
{ 
public: 
Foo() = delete; 
};

怎么样:

//deleted constructor
class Foo
{ 
public: 
Foo() = delete;     
public:
static void foo();
};
void Foo::foo()
{
Foo f;    //illegal
}

//private constructor
class Foo
{ 
private: 
Foo() {}     
public:
static void foo();
};
void Foo::foo()
{
Foo f;    //legal
}

它们基本上是不同的东西。private告诉您,只有类的成员才能调用该方法或访问该变量(当然还有朋友)。在这种情况下,该类(或任何其他成员)的static方法调用类的private构造函数是合法的。这不适用于已删除的构造函数。

在这里采样。

为什么要显式删除构造函数?

另一个原因:

当我想确保使用初始值设定项调用类时,我会使用delete。 我认为这是一种非常优雅的方式,无需运行时检查即可实现这一目标。

C++编译器会为您执行此检查。

class Foo
{
public:
Foo() = delete;
Foo(int bar) : m_bar(bar) {};
private:
int m_bar;
};

这个 -非常简化- 代码确保没有像这样的实例化(默认构造):Foo foo;

tl;博士

显式delete始终有效,并提供更易于理解的错误消息。不应再使用私人声明来阻止呼叫!

详细说明

如果声明函数私有,则仍然可以调用它。例如,对于构造函数,来自staticfriend函数。

通过显式删除函数,您可以表示永远不能使用它。这尤其会导致在尝试调用此类函数时出现清晰易懂的错误消息。

在下面的类中,我们没有默认构造函数,因为没有有意义的方法来实现它。该类包含一个引用,它需要一些对象来指向。

class Foo {
private:
int& ref_;
public:
Foo(int& ref) : ref_(ref) {}
};
int main() {
Foo();
}
$ g++ main.cpp
main.cpp: In function ‘int main()’:
main.cpp:10:9: error: no matching function for call to ‘Foo::Foo()’
10 |     Foo();
|         ^
main.cpp:6:5: note: candidate: ‘Foo::Foo(int&)’
6 |     Foo(int& ref) : ref_(ref) {}
|     ^~~
main.cpp:6:5: note:   candidate expects 1 argument, 0 provided
main.cpp:1:7: note: candidate: ‘constexpr Foo::Foo(const Foo&)’
1 | class Foo {
|       ^~~
main.cpp:1:7: note:   candidate expects 1 argument, 0 provided
main.cpp:1:7: note: candidate: ‘constexpr Foo::Foo(Foo&&)’
main.cpp:1:7: note:   candidate expects 1 argument, 0 provided

删除构造函数会使错误消息简短且易于理解。

class Foo {
private:
int& ref_;
public:
Foo() = delete;
Foo(int& ref) : ref_(ref) {}
};
int main() {
Foo();
}
$ g++ main.cpp
main.cpp: In function ‘int main()’:
main.cpp:11:9: error: use of deleted function ‘Foo::Foo()’
11 |     Foo();
|         ^
main.cpp:6:5: note: declared here
6 |     Foo() = delete;
|     ^~~

如果我们通过私有声明(没有定义,这是不可能的)来解决这个问题,消息一开始看起来很相似。

class Foo {
private:
int& ref_;
Foo();
public:
Foo(int& ref) : ref_(ref) {}
};
int main() {
Foo();
}
$ g++ main.cpp
main.cpp: In function ‘int main()’:
main.cpp:12:9: error: ‘Foo::Foo()’ is private within this context
12 |     Foo();
|         ^
main.cpp:5:5: note: declared private here
5 |     Foo();
|     ^~~

只要您不从可访问类的私有部分的上下文中调用构造函数,这就可以很好地工作。如上所述,这可以是例如静态或友好函数。原则上,它也可以是正常功能,尽管这种用例相当罕见。

class Foo {
private:
int& ref_;
Foo();
public:
Foo(int& ref) : ref_(ref) {}
static Foo create() {
return Foo(); // compiles fine
}
void foo() {
Foo(); // compiles fine
}
friend void bar();
};
void bar() {
Foo(); // compiles fine
}
int main() {}
g++ -c -o main.o main.cpp

这编译完全没有问题,编译只是假设其他地方会有Foo::Foo()的定义。一旦链接器必须从中生成可执行文件,它就会报告缺少的定义。

$ g++ main.o
# or as one step with compilation and linking
$ g++ main.cpp
/usr/bin/ld: /tmp/ccnhLDsv.o: in function `bar()':
main.cpp:(.text+0x23): undefined reference to `Foo::Foo()'
collect2: error: ld returned 1 exit status

此类错误通常非常难以调试,因为它们卡在代码库中的任何位置,并且您不知道错误在哪个文件,更不用说在哪一行了。

另一方面,显式delete在错误所在的三个位置提供三条精确的错误消息。

class Foo {
private:
int& ref_;
public:
Foo() = delete;
Foo(int& ref) : ref_(ref) {}
static Foo create() {
return Foo(); // error
}
void foo() {
Foo(); // error
}
friend void bar();
};
void bar() {
Foo(); // error
}
int main() {}
$ g++ main.cpp
main.cpp: In static member function ‘static Foo Foo::create()’:
main.cpp:10:20: error: use of deleted function ‘Foo::Foo()’
10 |         return Foo(); // error
|                    ^
main.cpp:6:5: note: declared here
6 |     Foo() = delete;
|     ^~~
main.cpp: In member function ‘void Foo::foo()’:
main.cpp:14:13: error: use of deleted function ‘Foo::Foo()’
14 |         Foo(); // error
|             ^
main.cpp:6:5: note: declared here
6 |     Foo() = delete;
|     ^~~
main.cpp: In function ‘void bar()’:
main.cpp:21:9: error: use of deleted function ‘Foo::Foo()’
21 |     Foo(); // error
|         ^
main.cpp:6:5: note: declared here
6 |     Foo() = delete;
|     ^~~

其他信息

void foo(int need_integer) {}
int main() {
foo(5.4); // might trigger a warning, but compiles
}

请注意,delete也可用于正常功能。例如,防止隐式转换。

void foo(int need_integer) {}
void foo(double) = delete;
int main() {
foo(5);   // okay
foo(5.4); // error
}
$ g++ main.cpp
main.cpp: In function ‘int main()’:
main.cpp:6:8: error: use of deleted function ‘void foo(double)’
6 |     foo(5.4); // error
|     ~~~^~~~~
main.cpp:2:6: note: declared here
2 | void foo(double) = delete;
|      ^~~

我在LLVM的源代码中遇到了默认的ctor声明为"已删除"(例如在AlignOf.h中)。关联的类模板通常位于名为"llvm::d etail"的特殊命名空间中。我认为那里的全部目的是他们只将那个类视为一个辅助类。他们从未打算实例化它们;仅在其他类模板的上下文中使用它们,并在编译时运行一些元编程技巧。

例如,有这个 AlignmentCalcImpl 类模板,它仅在另一个名为 AlignOf 的类模板中使用,作为 sizeof(.) 运算符的参数。该表达式可以在编译时计算;并且不需要实例化模板 ->那么为什么不声明默认的 ctor delete 来表达这个意图。

但这只是我的假设。

相关文章: