编译器为类创建的所有成员函数是什么?这是否一直发生?像析构函数。我关心的是它是否是为所有类创建的,为什么需要默认构造函数?
5 回答
C++98/03
如果需要它们,
- 除非您声明自己的任何构造函数,否则编译器将为您生成默认构造函数。
- 除非您声明自己的,否则编译器将为您生成一个复制 构造函数。
- 除非您声明自己的,否则编译器将为您生成一个复制 赋值运算符。
- 除非您声明自己的,否则编译器将为您生成一个析构函数。
正如 Péter 在有用的评论中所说,所有这些仅在需要时由编译器生成。(不同的是,当编译器无法创建它们时,只要不使用它们就可以。)
C++11
C++11 添加了以下规则,对于 C++14 也是如此(towi 的学分,请参阅此评论):
- 编译器生成移动 构造函数,如果
- 没有用户声明的复制 构造函数,并且
- 没有用户声明的复制 赋值运算符,并且
- 没有用户声明的移动 赋值运算符和
- 没有用户声明的析构函数,
- 它没有标记为
delete
d, - 并且所有成员和基地都是可移动的。
- 类似地,对于移动 赋值运算符,如果
- 没有用户声明的复制 构造函数,并且
- 没有用户声明的复制 赋值运算符,并且
- 没有用户声明的移动 构造函数,并且
- 没有用户声明的析构函数,
- 它没有标记为
delete
d, - 并且所有成员和基地都是可移动的。
请注意,这些规则比 C++03 规则更复杂一些,并且在实践中更有意义。
为了更容易理解上面的内容:
class Thing {
public:
Thing(); // default constructor
Thing(const Thing&); // copy c'tor
Thing& operator=(const Thing&); // copy-assign
~Thing(); // d'tor
// C++11:
Thing(Thing&&); // move c'tor
Thing& operator=(Thing&&); // move-assign
};
进一步阅读:如果您是 C++ 初学者,请考虑一种不需要您实现五项中的任何一项的设计,即最初来自Martinho Fernandes撰写的文章的零规则。
您的意思是通过“创建”来“定义”吗?
$12.1 - “默认构造函数 (12.1)、复制构造函数和复制赋值运算符 (12.8) 和析构函数 (12.4) 是特殊的成员函数。
如果“创建”意味着“定义”,那么这里是 C++ 标准的重要部分。
- 一个类的隐式声明的默认构造函数在用于创建其类类型的对象时被隐式定义(1.8)。
- 如果一个类没有用户声明的析构函数,则隐式声明析构函数。隐式声明的析构函数在用于销毁其类类型的对象时被隐式定义。
- 如果类定义没有显式声明复制构造函数,则隐式声明一个。如果隐式声明的复制构造函数用于从其类类型的对象或从其类类型派生的类类型的对象的副本初始化其类类型的对象,则它是隐式定义的)。
- 如果类定义没有显式声明复制赋值运算符,则隐式声明一个。当为其类类型的对象分配其类类型的值或从其类类型派生的类类型的值时,隐式声明的复制赋值运算符被隐式定义。
C++17 N4659 标准草案
https://github.com/cplusplus/draft/blob/master/papers/n4659.pdf 6.1“声明和定义”有一个注释,可能总结了所有这些:
3 [注:在某些情况下,C++ 实现隐式定义默认构造函数 (15.1)、复制构造函数 (15.8)、移动构造函数 (15.8)、复制赋值运算符 (15.8)、移动赋值运算符 (15.8) 或析构函数 ( 15.4) 成员函数。— 尾注] [ 示例:给定
#include <string> struct C { std::string s; // std::string is the standard library class (Clause 24) }; int main() { C a; C b = a; b = a; }
该实现将隐式定义函数以使 C 的定义等效于
struct C { std::string s; C() : s() { } C(const C& x): s(x.s) { } C(C&& x): s(static_cast<std::string&&>(x.s)) { } // : s(std::move(x.s)) { } C& operator=(const C& x) { s = x.s; return *this; } C& operator=(C&& x) { s = static_cast<std::string&&>(x.s); return *this; } // { s = std::move(x.s); return *this; } ~ C() { } };
—结束示例]
声明它们的条件在:自动生成默认/复制/移动 ctor 和复制/移动赋值运算符的条件?
确保某些东西具有默认值的一种很酷的方法是尝试使用它= default
,如下所述:在类的函数声明之后“默认”是什么意思?
下面的示例做到了这一点,并且还练习了所有隐式定义的函数。
#include <cassert>
#include <string>
struct Default {
int i;
Default() = default;
Default(const Default&) = default;
Default& operator=(Default&) = default;
Default& operator=(const Default&) = default;
Default(Default&&) = default;
Default& operator=(Default&&) = default;
~Default() = default;
};
struct Instrument {
int i;
static std::string last_call;
Instrument() { last_call = "ctor"; }
Instrument(const Instrument&) { last_call = "copy ctor"; }
Instrument& operator=(Instrument&) { last_call = "copy assign"; return *this; }
Instrument& operator=(const Instrument&) { last_call = "copy assign const"; return *this; }
Instrument(Instrument&&) { last_call = "move ctor"; }
Instrument& operator=(Instrument&&) { last_call = "move assign"; return *this; }
~Instrument() { last_call = "dtor"; }
};
std::string Instrument::last_call;
int main() {
// See what the default constructors are doing.
{
// Default constructor.
Default ctor;
// i is uninitialized.
// std::cout << ctor.i << std::endl;
ctor.i = 1;
// Copy constructor.
Default copy_ctor(ctor);
assert(copy_ctor.i = 1);
// Copy assignment.
Default copy_assign;
copy_assign = ctor;
assert(copy_assign.i = 1);
// Copy assignment const.
const Default const_ctor(ctor);
Default copy_assign_const;
copy_assign_const = const_ctor;
assert(copy_assign_const.i == 1);
// Move constructor.
Default move_ctor(std::move(ctor));
assert(move_ctor.i == 1);
// Move assignment.
Default move_assign;
move_assign = std::move(ctor);
assert(move_assign.i == 1);
}
// Check that the constructors are called by these calls.
{
// Default constructor.
Instrument ctor;
assert(Instrument::last_call == "ctor");
// Copy constructor.
Instrument copy_ctor(ctor);
assert(Instrument::last_call == "copy ctor");
// Copy assignment.
copy_ctor = ctor;
assert(Instrument::last_call == "copy assign");
// Copy assignment const.
const Instrument const_ctor(ctor);
Instrument copy_assign_const;
copy_assign_const = const_ctor;
assert(Instrument::last_call == "copy assign const");
// Move constructor.
Instrument move_ctor(std::move(ctor));
assert(Instrument::last_call == "move ctor");
// Move assignment.
Instrument move_assign;
move_assign = std::move(ctor);
assert(Instrument::last_call == "move assign");
// Destructor.
{
Instrument dtor;
}
assert(Instrument::last_call == "dtor");
}
}
经 GCC 7.3.0 测试:
g++ -std=c++11 implicitly_defined.cpp
默认情况下,如果用户没有实现,编译器会在类中添加一些成员函数。那些被称为四大:
- 默认构造函数
- 复制构造函数
- 复制运算符(赋值)
- 析构函数
根据成员的类型以及您自己提供的列出的成员函数,这些不会全部生成。
其他答案已经告诉您创建了什么,并且编译器只能在使用时生成它们。
我担心的是它是否是为所有类创建的......
为什么担心?认为它在可执行文件中创建了不需要的代码?不太可能,但您可以轻松地检查您的环境。
或者您可能担心它可能不会在您需要时创建构造函数?没什么好担心的……它们总是在需要时创建,而不是由用户提供。
...为什么需要默认构造函数?
因为类内部可能有对象,它们有自己的析构函数,需要系统地调用。例如,给定...
struct X
{
std::string a;
std::string b;
};
...默认析构函数确保 a 和 b 的析构函数运行。