Search code examples
c++unit-testingc++14virtualunique-ptr

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


Coming from the Java/PHP world, I am still new to C++. Some simple things to do in other languages are a bit trickier to do in C++.

My main issue is the following. Right now, I have a class (ie. "Something") for which the constructor is injected with a virtual class dependency (ie. a children of "Base"). Then, the constructor stores this injected instance in a unique_ptr<Base> class field (using the clone idiom). This works well at the application level, everything seems to works as expected. Here is the sample code:

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

But to make sure it does, I wrote unit tests to test its behavior. In those tests, I want to assert the injected dependencies methods are actually called. So logically, injecting a "spy" dependency should do the trick.

Here is what I did at first:

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

This is the main function I use to this this out:

int main() {
    SpyDerived derived;
    Something something(derived);

    derived.setSomeState(true);
    something.sayHello();
}

For obvious reasons, someState value on print is always false. I get that the Derived instances in Something is a new copy of Derived and no longer the one that was created in the main function.

So basically, what I am trying to achieve here is to have the Something class always use the SpyDerived instance created in the main function. Is there any way I could make this work. I am trying to avoid changing the design just for test purposes.

I am using MSVC 2015 to compile the code. Keep in mind that smart pointers, C++ idioms, copy/move constructors are fairly new concepts for me.

Thanks for your help.


Solution

  • Well, do you want to clone your instance, or simply reference that instance?

    The clone idiom is made to copy the instance of a class, making the new instance independent of the old instance.

    You are basically making this, in term of 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();
    
    ?>
    

    You see this? $base is cloned. Something::$base is a copy.

    So in PHP, what would you do to solve that problem?

    Simple! Remove that clone, no copies!


    Well, in C++, this is the same thing. If you have an object pointer and don't want to clone it, don't actually call the clone method.

    We will change your class to, like PHP, contain a reference to the object. We will start by making Something contain a non owning reference:

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

    In C++, a reference does not own the object. If the object is destroyed, all reference pointing to that object will point to a dead object.

    As you can notice, your tests stays the same and work:

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

    If you want Something be the owner of Base, then use 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;
    };
    

    Beware that the ownership of base should be transferred from the caller to the something class. That transfer is express through that std::move thing, because we are moving the ownership of that resource.

    Then in your tests:

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

    Since Something is owner of derived, the non-owning reference derived_ref will point to a dead object if something dies before.