这是我在过去两个月学习 Move 语义期间到目前为止所理解的:
移动构造函数在类定义末尾隐式插入
SomeClass::operator=(const SomeClass&) = delete
,禁用复制赋值运算符
这种行为是为了强制执行 5 规则,其中使用 Move 构造函数 或 Move 赋值操作符 - 所有以下 5 条规则(或在某些情况下的一部分)都应该被实现:
但是,如果我想重用基类(或父类/超类)中的赋值运算符 - 只需放置
using SomeBaseClass::operator=;
在实现 Move Constructor 的子类中似乎不够,正如我们之前建立的那样 - 当定义 Move Constructor 时,编译器隐式地将其插入到子类定义的末尾。
class SomeClass: public SomeBaseClass {
public:
SomeClass(SomeClass&& other);
using SomeBaseClass::operator=;
...
// Compiler: let me help you!
SomeClass& SomeClass::operator=(const SomeClass&) = delete;
}
...匹配时似乎有更高的优先级,遮蔽
using ~~:operator=
,导致以下错误:
class SomeClass a, b;
a = b; // Use of deleted func!
显然,再次在子类上编写运算符,在内部仅包装基类的运算符应该并且确实有效。每个子类只需多 3 行:
// using void to give up ability to chain like a = b = c
void SomeSubclass::operator=(const SomeSubclass& other) {
SomeBaseClass::operator=(other);
}
...但感觉它首先违背了使用继承的目的。有没有更好的替代方法?
这是代表此行为的 MRE(Compiler Explorer Live):
#include <iostream>
class Derived;
// Interface class to reuse & enforce specific set of operations
// over multiple sibling subclasses
class SomeInterface {
protected:
// Function to allow subclass usage inside predefined operations
virtual Derived& get_derived() = 0;
public:
// destructor / move / copy assignment defined to satisfy Rule of 5 in subclasses
virtual ~SomeInterface() = default;
// Giving up ability to do a = b = c
void operator=(const Derived& other);
void operator=(Derived&& other);
};
// ...Now I'm not sure if this count as interface class, still doesn't have member data tho!
// Subclass implementing Interface
class Derived : public SomeInterface {
protected:
int some_data;
Derived& get_derived() override;
public:
Derived(int data) : some_data(data) {}
Derived(Derived&& other) noexcept;
Derived(const Derived& other);
// Some data accessors
int& get_data() { return some_data; }
const int& get_data() const { return some_data; }
// Using predefined operators provided by interface
using SomeInterface::operator=;
// No errores if we define wrapper
//void operator=(const Derived& other) {
// SomeInterface::operator=(other);
//}
};
// --- Implementations ---
Derived& Derived::get_derived() {
return *this;
}
Derived::Derived(Derived&& other) noexcept {
std::swap(some_data, other.get_data());
}
Derived::Derived(const Derived& other) {
some_data = other.get_data();
}
void SomeInterface::operator=(const Derived& other) {
get_derived().get_data() = other.get_data();
}
int main() {
Derived a(2);
Derived b(19);
a = b; // <-- Use of deleted function Derived& Derived::operator=(const Derived&)
std::cout << a.get_data() << std::endl;
}
这样的结构确实看起来很奇怪,这是我想出它背后的逻辑:
有 A、B、C 类。我希望(几乎)所有 A、B、C 的功能都可以在 D 和 E 类中使用。
每个A、B、C类定义了2~30个成员函数,背后有几千个代码。
如果我们通过将类
A
、B
、C
中的函数复制到 D
& E
中来实现此目的,我们将需要手动复制粘贴数千行,让大量修改实施以使其发挥作用。
这显然维护起来非常痛苦,因为任何类函数的任何单个更改都需要更新 5 个类以维护统一的用户端接口(而不是通过多态性)。
实际案例是我正在为其重新发明轮子的 Json 库。
(我知道现代 C++ 的 Json - 就在这个地方,我什至不允许使用 MIT/Unlicense 许可的开源库)
JsonValue、JsonArray、JsonObject 的功能应该在 JsonArrayProxy 和 JsonObjectProxy 中可用,以支持如下表达式:
arr[0]["some_key"].append({"key": "val"})
其中每个下标运算符返回代理类区分只读访问和只写访问。
我设法通过手动复制粘贴来实现这些,但由于可维护性问题,我决定创建一个接口类,该类不定义其数据类,但定义了应共同共享的所有操作。
// some mock-up class structure
// Interfaces
class JsonStringfierInterface;
class JsonValueInterface: public JsonStringfier;
class JsonObjectInterface: public JsonStringfier;
class JsonArrayInterface: public JsonStringfier;
class JsonProxyInterface
: public JsonValueInterface,
public JsonObjectInterface,
public JsonArrayInterface;
// Implemntation
class JsonValue: public JsonValueInterface;
class JsonObject: public JsonObjectInterface;
class JsonArray: public JsonArrayInterface;
class JsonObjectProxy: public JsonProxyInterface;
class JsonArrayProxy: public JsonProxyInterface;
因此,即使使用我能想到的最棘手的 JSON 格式,这些东西也能正常工作 - 然而,我很好奇是否有更好的方法来做到这一点而不包装复制赋值运算符。
您应该遵循零规则,而不是五规则。您的代理类不拥有任何资源。
摆脱所有特殊成员解决你的问题。