提问人: 提问时间:8/1/2023 更新时间:8/2/2023 访问量:245
复制语义和向量
Copy semantics and vectors
问:
我正在处理分配内存供内部使用的对象。目前,它们不可复制。
例如
class MyClass
{
public:
MyClass() { Store = new int; }
~MyClass() { delete Store; }
private:
int* Store;
};
我想添加允许赋值所需的内容,包括复制和移动语义,并且还能够将它们存储在向量中(使用移动语义)。我不想使用智能指针,并希望保持简单。
我应该定义哪些类成员?如何强制复制分配或移动分配?按值或引用传递对象时将执行哪个副本?C++ 的后续版本的实现会有所不同吗?
例如
MyClass A, B;
A = B; // How to force copy or move ?
std::vector<MyClass> V = { A, B };
答:
“我应该定义哪些班级成员?”
您需要遵循五法则,该法则需要定义:
- 破坏者
- Copy 构造函数
- Move 构造函数
- 复制分配运算符
- 移动赋值运算符
使用智能指针,您可能可以实现这些功能,基本上就是这样。这将是更简洁的解决方案。default
但是,由于您不想使用智能指针,因此您需要以旧的方式自己完成所有恶作剧。
“如何强制复制分配或移动分配?”
a = b; // copy-assignment
a = std::move(b); // move-assignment
std::move()
基本上将参数转换为 r 值引用,以便调用适当的运算符重载 (move-assignment)。static_cast
“按值或引用传递对象时将执行哪个副本?”
按值传递时,将创建副本。如果您想搬家,则需要再次使用。std::move()
通过引用传递时,不会进行复制或移动。您可以将其视为传递指向对象的指针(除了引用不可为空)。
“C++ 的后续版本的实现会有所不同吗?”
好吧,谁知道未来?但它不应该。通常,它们倾向于尽可能地保持向后兼容性,以避免破坏旧代码。但它有时会发生(例如,c++11 是一场革命,破坏某些东西是无法避免的,但这是必要的)。
无论如何,我严重怀疑五法则在未来的版本中会成为错误:)
可能的实现(示例):
class Foo
{
private:
int * store;
public:
//Default constructor
Foo() : store{nullptr}
{}
// Destructor
~Foo()
{
delete store;
}
// Copy constructor
Foo(const Foo & f) : store{f.store ? new int{*f.store} : nullptr}
{}
// Move constructor
Foo(Foo && f) noexcept : store{std::exchange(f.store, nullptr)}
{}
// Copy-assignment operator
Foo & operator=(const Foo & f)
{
if(!f.store)
{
delete store;
store = nullptr;
return *this;
}
if(!store)
store = new int{};
*store = *f.store;
return *this;
}
// Move-assignment operator
Foo & operator=(Foo && f) noexcept
{
using std::swap;
swap(store, f.store);
return *this;
}
};
评论
*store = *f.store;
store
int
如果您管理的数据是固定大小的,则可以将其直接放在类本身中:
class MyClass
{
Data data;
};
如果要管理的数据大小不同,则可以使用向量:
class MyClass
{
std::vector<unsigned char> buffer;
};
没有比这更简单的了。
评论
stl
reserve
你问了向量,所以......这是类.我把它写成一个练习。它的设计遵循了Arthur O'Dwyer在CppCon 2019上的演讲。请观看YouTube视频,标题为“回归基础:RAII和零法则”。tbx::Vector
为了更深入地关注你所问的复制语义,我只包括了五个“特殊”成员函数,以及几个其他的 ctor 和成员函数和 .虽然类实现了几乎所有的接口,但其他函数在这里没有出现。reserve
swap
Vector
std::vector
我留下了钩子,您可以在其中插入所需的任何代码,以防操作员尝试的分配失败。new
这不用作生产代码。首先,为什么要重新发明轮子?第二,有很多优化被省略了,第三,一些功能被省略了,例如,我没有提供完整的 ctor 补充或对分配器的支持。
template< typename T >
class Vector
{
public:
using value_type = T;
using size_type = std::size_t;
using iterator = value_type*;
using const_iterator = value_type const*;
private:
value_type* data_{ nullptr };
size_type capacity_{};
size_type size_{};
enum : size_type { zero, one };
public:
Vector() noexcept
= default;
explicit Vector(size_type const size)
: data_{ nullptr }, capacity_{ size }, size_{ size }
{
if (zero < size)
{
try { data_ = new value_type[size]; }
catch (std::bad_alloc const&) { throw; }
}
}
Vector (Vector const& that)
: data_{ nullptr }
, capacity_{ that.size_ } // "shrink to fit"
, size_{ that.size_ }
{
if (that.size_ != zero)
{
try { data_ = new value_type[that.size_]; }
catch (std::bad_alloc const&) { throw; }
std::copy(that.data_, that.data_ + size_, data_);
}
}
Vector (Vector&& that) noexcept
: data_ { std::exchange(that.data_, nullptr) }
, capacity_ { std::exchange(that.capacity_, zero) }
, size_ { std::exchange(that.size_, zero) }
{}
~Vector() {
delete[] data_;
}
Vector& operator=(Vector that) noexcept {
swap(that);
return *this;
}
void reserve(size_type const capacity) {
if (capacity_ < capacity) {
value_type* p{ nullptr };
try { p = new value_type[capacity]; }
catch (std::bad_alloc const&) { throw; }
if (data_ != nullptr)
std::copy(data_, data_ + size_, p);
capacity_ = capacity;
std::swap(data_, p);
delete[] p;
}
}
void swap(Vector& that) noexcept {
using std::swap;
swap(capacity_, that.capacity_);
swap(size_, that.size_);
swap(data_, that.data_);
}
friend void swap(Vector& a, Vector& b) noexcept {
a.swap(b);
}
};
评论
stl
std::vector
unique_ptr
shared_ptr
MyClass
MyClass
int
)