将 Move 构造函数与基类的 Copy 赋值运算符混合的优雅方法

问题描述 投票:0回答:1

问题说明

这是我在过去两个月学习 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 格式,这些东西也能正常工作 - 然而,我很好奇是否有更好的方法来做到这一点而不包装复制赋值运算符。


c++ inheritance copy-assignment
1个回答
0
投票

您应该遵循零规则,而不是五规则。您的代理类不拥有任何资源。

摆脱所有特殊成员解决你的问题

© www.soinside.com 2019 - 2024. All rights reserved.