如何针对C 中的克隆成语创建间谍类

How to create a spy class against the clone idiom in C++

本文关键字:成语 创建 间谍 何针      更新时间:2023-10-16

来自Java/PHP世界,我仍然是C 的新手。在其他语言中要做的一些简单的事情在C 中要做一些棘手。

我的主要问题是以下内容。现在,我有一个类(即"某物"),为构造函数注入了虚拟级别的依赖性(即"基础"的孩子)。然后,构造函数将此注入的实例存储在unique_ptr<Base>类字段中(使用克隆成语)。这在应用程序级别上效果很好,一切似乎都按预期工作。这是示例代码:

class Base {
    public:
        virtual std::unique_ptr<Base> clone() = 0;
        virtual void sayHello() const = 0;
};
class Something {
    public:
        explicit Something(Base &base) { this->base = base.clone(); }
        void sayHello() const { base->sayHello(); }
    private:
        std::unique_ptr<Base> base;
};

但是,为了确保确实如此,我编写了单元测试以测试其行为。在这些测试中,我想确定实际调用了注入的依赖性方法。因此,从逻辑上讲,注入"间谍"依赖性应该可以解决问题。

这是我起初所做的:

class SpyDerived : public Base {
    public:
        explicit SpyDerived() = default;
        SpyDerived(const SpyDerived &original) { this->someState = original.someState; }
        std::unique_ptr<Base> clone() override { return std::make_unique<SpyDerived>(*this); }
        void sayHello() const override { std::cout << "My state: " << someState << std::endl; }
        void setSomeState(bool value) { this->someState = value; }
    private:
        bool someState = false;
};

这是我使用的主要功能:

int main() {
    SpyDerived derived;
    Something something(derived);
    derived.setSomeState(true);
    something.sayHello();
}

出于明显的原因,someState打印值始终为false。我知道Something中的Derived实例是Derived的新副本,不再是在主函数中创建的副本。

基本上,我在这里要实现的目标是让Something类始终使用在主函数中创建的SpyDerived实例。有什么办法可以做这项工作。我试图避免仅出于测试目的更改设计。

我正在使用MSVC 2015来编译代码。请记住,聪明的指针,C 成语,复制/移动构造函数对我来说是相当新的概念。

感谢您的帮助。

好吧,您想克隆您的实例,还是简单地引用该实例?

克隆成语是制作以复制类的实例,使新实例独立于旧实例。

您基本上是按照PHP的术语来进行的:

<?php
interface Base {
    public function sayHello();
}
class SpyDerived implements Base {
    private $someState = false;
    public function sayHello() {
        echo 'My state: ' . $this->someState;
    }
}
class Something {
    public __construct(Base $base) { $this->base = clone $base; }
    public function sayHello() { $this->base->sayHello(); }
    private $base = null;
}
$derived = new SpyDerived;
$something = new Something($derived);
$derived->setSomeState(true);
$something->sayHello();
?>

你看到了吗?$base被克隆。 Something::$base复制

所以在PHP中,您将如何解决该问题?

简单!删除该克隆,没有副本!


好吧,在C 中,这是同一件事。如果您有对象指针并且不想克隆它,则实际上不要调用克隆方法。

我们将把您的课程更改为PHP,包含对对象的引用。我们将首先使Something包含一个非所有参考:

class Something {
    public:
        explicit Something(Base& b) : base{b} { }
        void sayHello() const { base.sayHello(); }
    private:
        // we simply contain a reference to the base
        Base& base;
};

在C 中,参考不拥有对象。如果对象被破坏,则所有指向该对象的参考都将指向一个死对象。

您会注意到,您的测试保持不变并起作用:

int main() {
    SpyDerived derived;
    Something something(derived);
    derived.setSomeState(true);
    something.sayHello();
}

如果您要Something成为Base的所有者,请使用std::unique_ptr<Base>

class Something {
    public:
        explicit Something(std::unique_ptr<Base> b) : base{std::move(b)} { }
        void sayHello() const { base->sayHello(); }
    private:
        std::unique_ptr<Base> base;
};

注意base的所有权应从呼叫者转移到某物类。该转移是通过std::move表达的,因为我们正在移动该资源的所有权。

然后在您的测试中:

int main() {
    auto derived = std::make_unique<SpyDerived>();
    // We want to keep a non-owning reference of derived
    // The star (*) operator of std::unique_ptr returns a reference to the pointed object
    auto& derived_ref = *derived;
    // We transfer the ownership of  derived to the `Something`
    Something something(std::move(derived));
    // Since derived is a reference to the object pointed by our pointer,
    // It will affect the value we found in `Something`, because they are
    // both pointing to the same instance.
    derived.setSomeState(true);
    something.sayHello();
}

由于Somethingderived的所有者,如果something死亡,则非持有参考derived_ref将指向一个死对象。