如何在 C++ 中针对克隆习语创建间谍 class

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

来自 Java/PHP 世界,我对 C++ 还是个新手。一些用其他语言做的简单事情用 C++ 做起来有点棘手。

我的主要问题如下。现在,我有一个 class(即 "Something"),构造函数为其注入了虚拟 class 依赖项(即 "Base" 的子级)。然后,构造函数将这个注入的实例存储在 unique_ptr<Base> class 字段中(使用克隆习惯用法)。这在应用程序级别运行良好,一切似乎都按预期运行。这是示例代码:

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;
};

但为了确保它确实如此,我编写了单元测试来测试它的行为。在那些测试中,我想断言注入的依赖项方法实际上被调用了。所以从逻辑上讲,注入 "spy" 依赖项应该可以解决问题。

这是我最初做的:

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 class 始终使用在 main 函数中创建的 SpyDerived 实例。有什么办法可以使这项工作。我试图避免仅出于测试目的而更改设计。

我正在使用 MSVC 2015 编译代码。请记住,智能指针、C++ 习语、copy/move 构造函数对我来说都是相当新的概念。

感谢您的帮助。

嗯,你是想克隆你的实例,还是只是引用那个实例?

克隆成语是复制一个class的实例,使新实例独立于旧实例。

根据 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 是一个 copy.

所以在 PHP 中,你会怎样解决这个问题?

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


嗯,在 C++ 中,这是一回事。如果您有一个对象指针并且不想克隆它,请不要实际调用克隆方法。

我们会将您的 class 更改为与 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 所有权 应该从调用者转移到某物 class。该转移是通过 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 将指向一个死对象。