我试着通过阅读这篇博客来了解零的规则.IMO,它说如果你声明自己的析构函数,那么不要忘记创建移动构造函数并将赋值移动为默认值.
示例:
class Widget {
public:
~Widget(); // temporary destructor
... // no copy or move functions
};
Run Code Online (Sandbox Code Playgroud)
"添加析构函数会产生禁用生成移动函数的副作用,但由于Widget是可复制的,因此用于生成移动的所有代码现在都会生成副本.换句话说,向类中添加析构函数可能会导致 - 有效的动作可以用可能效率低下的副本无声地替换".
Scott Meyers的上述文字在引言中提出了一些问题:
我发现Peter Sommerlads Slides(第32页)中提到的零法则非常引人注目.
虽然,我好像记得,有一个严格的规则,一个有定义虚拟析构函数,如果类有虚拟成员和实际的.
struct Base {
virtual void drawYourself();
virtual ~Base() {}
};
struct Derived : public Base {
virtual void drawYourself();
};
Run Code Online (Sandbox Code Playgroud)
析构函数的主体甚至可能是空的(它只需要vtbl中的条目).
我似乎记得在使用层次结构时
int main() {
Base *obj = new Derived{};
obj->drawYourself(); // virtual call to Derived::drawYourself()
delete obj; // Derived::~Derived() _must_ be called
}
Run Code Online (Sandbox Code Playgroud)
那么delete obj 调用正确的析构函数是很重要的.这是正确的,如果我完全省略了析构函数定义,它就不会变成虚拟的,因此会调用错误的数字吗?
struct Base {
virtual void drawYourself();
// no virtual destructor!
}; …Run Code Online (Sandbox Code Playgroud) 所以我一直在阅读关于零规则的内容.
简化版:我不明白这条规则的目的.三和五的规则有点像"经验法则",但是我不能用这个规则看到"经验法则"或任何其他特定意图.
详细版本:
我来引述一下:
具有自定义析构函数,复制/移动构造函数或复制/移动赋值运算符的类应专门处理所有权.其他类不应该有自定义析构函数,复制/移动构造函数或复制/移动赋值运算符.
这是什么意思?什么是所有权,拥有什么?他们还展示了一个示例代码(我猜它与介绍有关):
class rule_of_zero
{
std::string cppstring;
public:
rule_of_zero(const std::string& arg) : cppstring(arg) {}
};
Run Code Online (Sandbox Code Playgroud)
他们想要用什么表现出来,我真的迷失在这一点上.
此外,当您处理多态类并且析构函数被声明为public和virtual以及此块隐式移动的事实时,他们也在谈论场景.因此,您必须将它们全部声明为默认值:
class base_of_five_defaults
{
public:
base_of_five_defaults(const base_of_five_defaults&) = default;
base_of_five_defaults(base_of_five_defaults&&) = default;
base_of_five_defaults& operator=(const base_of_five_defaults&) = default;
base_of_five_defaults& operator=(base_of_five_defaults&&) = default;
virtual ~base_of_five_defaults() = default;
};
Run Code Online (Sandbox Code Playgroud)
这是否意味着每当你有一个带有析构函数的基类被声明为public和virtual时,你真的必须将所有其他特殊成员函数声明为默认值?如果是这样,我不明白为什么.
我知道这在一个地方很混乱.
我正在编写一个使用C接口创建的两个对象的类.对象看起来像:
typedef struct... foo_t;
foo_t* create_foo(int, double, whatever );
void delete_foo(foo_t* );
Run Code Online (Sandbox Code Playgroud)
(同样如此bar_t).因为C++ 11,我想将它们包装在智能指针中,所以我不必编写任何特殊方法.该类将拥有这两个对象的唯一所有权,因此unique_ptr逻辑上有意义......但我仍然需要编写一个构造函数:
template <typename T>
using unique_ptr_deleter = std::unique_ptr<T, void(*)(T*)>;
struct MyClass {
unique_ptr_deleter<foo_t> foo_;
unique_ptr_deleter<bar_t> bar_;
MyClass()
: foo_{nullptr, delete_foo}
, bar_{nullptr, delete_bar}
{ }
~MyClass() = default;
void create(int x, double y, whatever z) {
foo_.reset(create_foo(x, y, z));
bar_.reset(create_bar(x, y, z));
};
Run Code Online (Sandbox Code Playgroud)
另一方面shared_ptr,我不必编写构造函数,或使用类型别名,因为我可以直接delete_foo进入reset()- 虽然这会使我的可MyClass复制,我不希望这样.
MyClass使用unique_ptr语义编写并仍然遵守Zero规则的正确方法是什么?
在最近实施零规则主题下的超载期刊中,作者描述了我们如何避免编写五个操作符规则,因为编写它们的原因是:
这两个都可以通过使用智能指针来处理.
在这里,我对第二部分特别感兴趣.
请考虑以下代码段:
class Base
{
public:
virtual void Fun() = 0;
};
class Derived : public Base
{
public:
~Derived()
{
cout << "Derived::~Derived\n";
}
void Fun()
{
cout << "Derived::Fun\n";
}
};
int main()
{
shared_ptr<Base> pB = make_shared<Derived>();
pB->Fun();
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,正如文章的作者解释的那样,我们通过使用共享指针获得多态删除,这确实有效.
但是,如果我shared_ptr用a 替换unique_ptr,我不再能够观察到多态删除.
现在我的问题是,为什么这两种行为有所不同?为什么不shared_ptr照顾多态删除unique_ptr?
我有一个基类,我不想让派生类可复制.为了使一切都明确,我以这种方式实现它:
class A {
public:
A() = default;
virtual ~A() = default;
A(const A&) = delete;
A(const A&&) = delete;
A& operator=(const A&) = delete;
A& operator=(const A&&) = delete;
virtual void vFun() = 0;
};
class B : public A {
public:
B() = default;
virtual ~B() = default;
B(const B&) = delete;
B(const B&&) = delete;
B& operator=(const B&) = delete;
B& operator=(const B&&) = delete;
virtual void vFun() override {}
};
Run Code Online (Sandbox Code Playgroud)
这是做这种事的正确方法吗?根据我的知识和我所读到的,答案是肯定的,但我想在将其引入生产系统之前确定.
总结一下:1)几乎总是不应删除运算符.那是因为"有无限的东西需要可动性".2)对于抽象基类,允许编译器生成特殊成员函数更安全,并且如果存在这种必要性,则将删除移动到派生类中.
在澄清Orbit的“ Lightness Races”之后,我缩小了职位范围。
看完这篇文章:零规则,
我最了解了,但是我仍然想解决一些不清楚的问题:
1.看这句话:
如果类X的定义未明确声明move构造函数,则仅在以下情况下,将隐式声明为default:
X没有用户声明的副本构造函数,并且
X没有用户声明的副本分配运算符,
X没有用户声明的移动分配运算符,
X没有用户声明的析构函数,并且
move构造函数不会隐式定义为Delete。
是否应该全部5条语句共存(共享“和”关系)或仅其中一部分(共享“或”关系)?
2. “ 用户声明的 ”复制构造函数\复制赋值运算符...是什么意思?
是在.h文件中声明它(上述列表中的任何一个),但未实现用户声明的实现?
是在.h文件中声明它(上面列表中的任何一个),并指定“ = deleted ”或“ = default ”视为用户声明的?
是在.h文件中用空手镯{}声明它(上面的列表中的任何一个),并认为是用户声明的?
尊敬,
埃泰
我正在研究零度规则,并且对最终的一段代码有两个问题,这些代码证明了规则.
class module {
public:
explicit module(std::wstring const& name)
: handle { ::LoadLibrary(name.c_str()), &::FreeLibrary } {}
// other module related functions go here
private:
using module_handle = std::unique_ptr<void, decltype(&::FreeLibrary)>;
module_handle handle;
};
Run Code Online (Sandbox Code Playgroud)
在我学习的过程中std::move,我发现了一个奇怪的问题.
如果我只添加一个对完美程序无效的析构函数,我将收到编译错误.
#include <iostream>
using namespace std;
class M {
public:
int database = 0;
M &operator=(M &&other) {
this->database = other.database;
other.database = 0;
return *this;
}
M(M &&other) { *this = std::move(other); }
M(M &m) = default;
M() = default;
~M() { /* free db */ }
};
class B {
public:
M shouldMove;
//~B(){} //<--- ## Adding this line will cause compile error. ##
};
int main() {
B b;
B b2 = std::move(b); //## …Run Code Online (Sandbox Code Playgroud) 我有一个基类Base和一个派生类D,我想为我自动生成移动构造函数和移动赋值运算符.遵循Zero规则,我将所有内存管理留给编译器,只使用level-2类(没有原始指针,数组等):
#include <iostream>
class Base{
public:
Base(): a_(42) {}
virtual void show() { std::cout << "Base " << a_ << std::endl; }
private:
int a_;
};
class D : Base {
public:
D(): b_(666) {}
void show() { std::cout << "D " << b_ << std::endl; }
private:
int b_;
};
int main() {
Base b;
b.show();
D d;
d.show();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这应该是吧,对吗?
输入C++核心指南:
基类析构函数应该是公共的和虚拟的,或者是受保护的和非虚拟的.
啊,所以我想我必须添加一个析构函数Base.但这将取消自动生成的移动功能!
什么是干净的出路?
我在围绕所有权问题以及通过移动最大化性能方面遇到麻烦。想象一下这套模拟Excel工作簿的类的假设。
namespace Excel {
class Cell
{
public:
// ctors
Cell() = default;
Cell(std::string val) : m_val(val) {};
// because I have a custom constructor, I assume I need to also
// define copy constructors, move constructors, and a destructor.
// If I don't my understanding is that the private string member
// will always be copied instead of moved when Cell is replicated
// (due to expansion of any vector in which it is stored)? Or will
// it …Run Code Online (Sandbox Code Playgroud)