应该声明哪些必要的特殊成员函数?
What are the necessary special member functions that should to be declared?
我目前正在学习 C++。我了解了一些由编译器生成的特殊成员函数。
• 默认构造函数。 • 复制构造函数 • 移动构造函数 • 复制赋值运算符 • 移动赋值运算符 • 析构函数
我知道每个成员函数的功能。但我想知道这些成员函数中哪些是必需的并且应该声明(而不是由编译器生成),作为良好编码实践的一部分,牢记内存和效率。
But I wanna which of these member function are necessary and should be declared (instead of being generated by the compiler)
如果隐式生成的特殊成员函数执行您想要的操作,则应显式声明其中 none 个。
如果一些以其他方式生成的特殊成员函数没有按照您的意图执行,那么您必须显式声明它们,并提供您需要的实现。
如果您对 [destructor, copy/move constructor/assignment] 中的任何一个都有明确的非平凡定义,那么根据经验,您可能需要为它们中的每一个定义。
这里的默认构造函数有点奇怪。是否有默认构造与是否编写其他构造不一定有任何关系(尽管在实践中编写没有默认构造的移动构造函数可能很困难)。
在大多数情况下,您应该尝试遵循零规则 (http://en.cppreference.com/w/cpp/language/rule_of_three, http://www.nirfriedman.com/2015/06/27/cpp-rule-of-zero/),这意味着您不会为 class 编写任何这些成员函数。相反,选择正确表达您想要的各种资源的所有权语义的成员。成员 std::vector
可以按照您希望的方式正确处理复制和移动,因此具有此类成员的 class 不一定需要任何特殊成员。但是,如果您尝试将使用 new[]
创建的原始指针用作数组,那么您将需要处理许多此类问题,并且需要编写特殊的成员函数。
如果你需要写一个,你应该明确地考虑所有的,但这并不意味着你必须写所有的。有时您可能想要 =default
或 =delete
其中一些。一个简单的例子:
template <class T>
copy_ptr {
copy_ptr() = default;
copy_ptr(copy_ptr&&) = default;
copy_ptr& operator=(copy_ptr&&) = default;
copy_ptr(const copy_ptr& other)
: m_resource(make_unique<T>(*other.m_resource)
{}
copy_ptr& operator=(const copy_ptr& other) {
m_resource = make_unique<T>(*other.m_resource);
}
// define operator*, etc
std::unique_ptr<T> m_resource;
};
这是一个指针,它不像 unique_ptr
那样不可复制,而是对其所持有的对象进行深层复制。但是,我们能够使用 unique_ptr
中的默认构造函数、移动运算符和析构函数 "as-is",因此我们默认了它们。我们只重写了复制操作,做了最少的工作。
基本上,C++ 中特殊运算符的想法是将它们 "downwards" 推入更小的 objects/classes。它们通常单独负责管理该资源。更大的 class 协调业务逻辑的实体应该非常努力地将资源管理问题推迟到成员身上。
默认构造函数是其中的特例。如果一些成员变量需要一个不是这些成员默认值的初始状态,写一个默认构造函数。
其余的只有在您知道自己需要时才应该被覆盖。
在一些合理的情况下,您可能希望编写所列函数的自定义版本:
- 您的 class 具有成员变量,这些成员变量是指向已分配的动态内存的指针。
- 您的 class 具有作为成员的数据结构,用户不应将其完整版本传递到构造函数中。
- 您的 class 包含您不想为其使用默认复制模型的成员(例如,您想要深复制,但复制构造函数或赋值会生成浅复制)。
- 您的 class 有一个静态数据成员,在创建它的新实例时应该更改它。
每个用例的示例包括:
- STL 向量,动态分配其底层数组并在其析构函数中解除分配。
- STL 列表,将添加到它们的数据复制到列表节点,其内部结构不会暴露给最终用户。
- 任何 STL 容器 class 就是一个很好的例子。
- 需要访问昂贵资源的事物的数量。类似于
std::shared_ptr
. 的设计目标
如果一个 class 满足太多这些要求,您可能应该考虑更改您的设计以使用更多容器和标准库提供的实用程序。特别是如果一个 class 需要第四个 属性 和其他三个中的任何一个。与第 1-3 点和第 4 点相关的职责应由不同的 classes 处理。 重要的是要注意,拥有一种大型的、创建起来很昂贵的资源和拥有许多资源是不同类型的责任。
参考文献:
我目前正在学习 C++。我了解了一些由编译器生成的特殊成员函数。
• 默认构造函数。 • 复制构造函数 • 移动构造函数 • 复制赋值运算符 • 移动赋值运算符 • 析构函数
我知道每个成员函数的功能。但我想知道这些成员函数中哪些是必需的并且应该声明(而不是由编译器生成),作为良好编码实践的一部分,牢记内存和效率。
But I wanna which of these member function are necessary and should be declared (instead of being generated by the compiler)
如果隐式生成的特殊成员函数执行您想要的操作,则应显式声明其中 none 个。
如果一些以其他方式生成的特殊成员函数没有按照您的意图执行,那么您必须显式声明它们,并提供您需要的实现。
如果您对 [destructor, copy/move constructor/assignment] 中的任何一个都有明确的非平凡定义,那么根据经验,您可能需要为它们中的每一个定义。
这里的默认构造函数有点奇怪。是否有默认构造与是否编写其他构造不一定有任何关系(尽管在实践中编写没有默认构造的移动构造函数可能很困难)。
在大多数情况下,您应该尝试遵循零规则 (http://en.cppreference.com/w/cpp/language/rule_of_three, http://www.nirfriedman.com/2015/06/27/cpp-rule-of-zero/),这意味着您不会为 class 编写任何这些成员函数。相反,选择正确表达您想要的各种资源的所有权语义的成员。成员 std::vector
可以按照您希望的方式正确处理复制和移动,因此具有此类成员的 class 不一定需要任何特殊成员。但是,如果您尝试将使用 new[]
创建的原始指针用作数组,那么您将需要处理许多此类问题,并且需要编写特殊的成员函数。
如果你需要写一个,你应该明确地考虑所有的,但这并不意味着你必须写所有的。有时您可能想要 =default
或 =delete
其中一些。一个简单的例子:
template <class T>
copy_ptr {
copy_ptr() = default;
copy_ptr(copy_ptr&&) = default;
copy_ptr& operator=(copy_ptr&&) = default;
copy_ptr(const copy_ptr& other)
: m_resource(make_unique<T>(*other.m_resource)
{}
copy_ptr& operator=(const copy_ptr& other) {
m_resource = make_unique<T>(*other.m_resource);
}
// define operator*, etc
std::unique_ptr<T> m_resource;
};
这是一个指针,它不像 unique_ptr
那样不可复制,而是对其所持有的对象进行深层复制。但是,我们能够使用 unique_ptr
中的默认构造函数、移动运算符和析构函数 "as-is",因此我们默认了它们。我们只重写了复制操作,做了最少的工作。
基本上,C++ 中特殊运算符的想法是将它们 "downwards" 推入更小的 objects/classes。它们通常单独负责管理该资源。更大的 class 协调业务逻辑的实体应该非常努力地将资源管理问题推迟到成员身上。
默认构造函数是其中的特例。如果一些成员变量需要一个不是这些成员默认值的初始状态,写一个默认构造函数。
其余的只有在您知道自己需要时才应该被覆盖。
在一些合理的情况下,您可能希望编写所列函数的自定义版本:
- 您的 class 具有成员变量,这些成员变量是指向已分配的动态内存的指针。
- 您的 class 具有作为成员的数据结构,用户不应将其完整版本传递到构造函数中。
- 您的 class 包含您不想为其使用默认复制模型的成员(例如,您想要深复制,但复制构造函数或赋值会生成浅复制)。
- 您的 class 有一个静态数据成员,在创建它的新实例时应该更改它。
每个用例的示例包括:
- STL 向量,动态分配其底层数组并在其析构函数中解除分配。
- STL 列表,将添加到它们的数据复制到列表节点,其内部结构不会暴露给最终用户。
- 任何 STL 容器 class 就是一个很好的例子。
- 需要访问昂贵资源的事物的数量。类似于
std::shared_ptr
. 的设计目标
如果一个 class 满足太多这些要求,您可能应该考虑更改您的设计以使用更多容器和标准库提供的实用程序。特别是如果一个 class 需要第四个 属性 和其他三个中的任何一个。与第 1-3 点和第 4 点相关的职责应由不同的 classes 处理。 重要的是要注意,拥有一种大型的、创建起来很昂贵的资源和拥有许多资源是不同类型的责任。
参考文献: