C++:如果我不使用移动,我还需要关心复制控制吗?
C++: Need I still care copy control if I don't use move?
复制控制似乎与默认构造函数、复制构造函数、复制赋值运算符、析构函数等有关,乘以用户编写的和合成的,加上移动复制/赋值。
听起来很复杂。我发现很难记住什么时候合成什么。
只是想知道,如果我不使用 move in constructor 或 assignment operator,我还需要关心它们之间的区别吗?
在用户代码中,您几乎不需要编写析构函数,因此 copy/move 构造函数和赋值运算符。这是因为您将组成 classes 个支持 RAII 的对象。
我们唯一需要编写析构函数的情况是我们正在控制非 RAII 资源,例如从 C 风格或原始 C++ 库导出的文件句柄或数据库连接。
在这种情况下,我们只需将资源包装在 unique_ptr(使用自定义删除器)或 shared_ptr(同样,使用自定义删除器)中,我们就完成了。
这给我们留下了 2 个场景:
多态接口的基础 class(不会被 shared_ptr 控制)——在这种情况下,我们必须编写一个虚拟析构函数,然后可能实现 move/copy 就默认实现而言。
A class 是可移动的(因为它拥有一个 unique_ptr,比方说?)并且我们希望它是可复制的。现在我们被迫实现复制操作和默认移动操作。
还有一些其他极端情况,例如如果您的 class 拥有一个不可复制的互斥体。但是,如果您的 class 拥有互斥锁,则要求它可复制可能已经是一个设计错误。无论如何,这时候你应该已经记住了 copy/assignment/move 规则。
一些例子:
struct owns_a_file_movable
{
struct file_deleter {
void operator()(FILE* f) const noexcept {
fclose(f);
}
};
// RAII class now, moveable. Copy would not make sense.
std::unique_ptr<FILE, file_deleter> file_;
};
struct owns_a_file_copyable
{
struct file_deleter {
void operator()(FILE* f) const noexcept {
fclose(f);
}
};
// construct from string
owns_a_file_copyable(std::string fname)
: path_(std::move(fname))
, file_(fopen(path_, "r"), file_deleter())
{
}
// we want it to be copyable. In our case, a copy will open another
// instance of the file. so we must store the filename.
owns_a_file_copyable(owns_a_file_copyable const& r)
: path_(t.path_)
, file_(fopen(path_, "r"), file_deleter())
{}
owns_a_file_copyable& operator=(owns_a_file_copyable const& r)
{
auto tmp = r;
std::swap(path_, tmp.path_); // changed: was r.path_ which was wrong
std::swap(file_, tmp.file_);
return *this;
}
owns_a_file_copyable(owns_a_file_copyable&&) = default;
owns_a_file_copyable& operator=(owns_a_file_copyable&&) = default;
// std::string is fully RAII
std::string path_;
// RAII class now, moveable
std::unique_ptr<FILE, file_deleter> file_;
};
struct how_most_classes_should_be
{
// no destructors, copy operators, assignment or move - it's all
// generated for you.
std::string this_;
std::string that_;
std::shared_ptr<const OtherThing> shared_other_; // note - shared semantics
std::function<void()> closure_; // these are copyable too
};
阿索斯
什么触发了什么?
// ordinary constructor
auto y = owns_a_file_copyable("/users/rhodges/foo.txt");
// copy constructor: owns_a_file_copyable(owns_a_file_copyable const&)
auto x = y;
// copy assignment: owns_a_file_copyable& operator-(owns_a_file_copyable const&)
x = y
// move-constructor: owns_a_file_copyable(owns_a_file_copyable &&)
auto z = std::move(x);
// move-assignment: owns_a_file_copyable& operator-(owns_a_file_copyable &&)
z = std::move(y);
// also move-constructor
extern owns_a_file_copyable make_file();
auto w = make_file();
// this time move-assignment
w = make_file();
复制控制似乎与默认构造函数、复制构造函数、复制赋值运算符、析构函数等有关,乘以用户编写的和合成的,加上移动复制/赋值。
听起来很复杂。我发现很难记住什么时候合成什么。
只是想知道,如果我不使用 move in constructor 或 assignment operator,我还需要关心它们之间的区别吗?
在用户代码中,您几乎不需要编写析构函数,因此 copy/move 构造函数和赋值运算符。这是因为您将组成 classes 个支持 RAII 的对象。
我们唯一需要编写析构函数的情况是我们正在控制非 RAII 资源,例如从 C 风格或原始 C++ 库导出的文件句柄或数据库连接。
在这种情况下,我们只需将资源包装在 unique_ptr(使用自定义删除器)或 shared_ptr(同样,使用自定义删除器)中,我们就完成了。
这给我们留下了 2 个场景:
多态接口的基础 class(不会被 shared_ptr 控制)——在这种情况下,我们必须编写一个虚拟析构函数,然后可能实现 move/copy 就默认实现而言。
A class 是可移动的(因为它拥有一个 unique_ptr,比方说?)并且我们希望它是可复制的。现在我们被迫实现复制操作和默认移动操作。
还有一些其他极端情况,例如如果您的 class 拥有一个不可复制的互斥体。但是,如果您的 class 拥有互斥锁,则要求它可复制可能已经是一个设计错误。无论如何,这时候你应该已经记住了 copy/assignment/move 规则。
一些例子:
struct owns_a_file_movable
{
struct file_deleter {
void operator()(FILE* f) const noexcept {
fclose(f);
}
};
// RAII class now, moveable. Copy would not make sense.
std::unique_ptr<FILE, file_deleter> file_;
};
struct owns_a_file_copyable
{
struct file_deleter {
void operator()(FILE* f) const noexcept {
fclose(f);
}
};
// construct from string
owns_a_file_copyable(std::string fname)
: path_(std::move(fname))
, file_(fopen(path_, "r"), file_deleter())
{
}
// we want it to be copyable. In our case, a copy will open another
// instance of the file. so we must store the filename.
owns_a_file_copyable(owns_a_file_copyable const& r)
: path_(t.path_)
, file_(fopen(path_, "r"), file_deleter())
{}
owns_a_file_copyable& operator=(owns_a_file_copyable const& r)
{
auto tmp = r;
std::swap(path_, tmp.path_); // changed: was r.path_ which was wrong
std::swap(file_, tmp.file_);
return *this;
}
owns_a_file_copyable(owns_a_file_copyable&&) = default;
owns_a_file_copyable& operator=(owns_a_file_copyable&&) = default;
// std::string is fully RAII
std::string path_;
// RAII class now, moveable
std::unique_ptr<FILE, file_deleter> file_;
};
struct how_most_classes_should_be
{
// no destructors, copy operators, assignment or move - it's all
// generated for you.
std::string this_;
std::string that_;
std::shared_ptr<const OtherThing> shared_other_; // note - shared semantics
std::function<void()> closure_; // these are copyable too
};
阿索斯
什么触发了什么?
// ordinary constructor
auto y = owns_a_file_copyable("/users/rhodges/foo.txt");
// copy constructor: owns_a_file_copyable(owns_a_file_copyable const&)
auto x = y;
// copy assignment: owns_a_file_copyable& operator-(owns_a_file_copyable const&)
x = y
// move-constructor: owns_a_file_copyable(owns_a_file_copyable &&)
auto z = std::move(x);
// move-assignment: owns_a_file_copyable& operator-(owns_a_file_copyable &&)
z = std::move(y);
// also move-constructor
extern owns_a_file_copyable make_file();
auto w = make_file();
// this time move-assignment
w = make_file();