具有任意构造函数参数的 C++ 完美通用抽象工厂

C++ Perfect Generic Abstract Factory with arbitrary Constructor Arguments

对于单元测试,我正在尝试创建满足以下要求的工厂:

目的是用这个工厂进行依赖注入,以便能够用模拟对象交换不属于测试一部分的对象。

这是我目前的情况:

#include <memory>
#include <iostream>

//"perfect factory method"
//usage: std::unique_ptr<Type> pt(create<Type>(Type-constructor-arguments));
template <typename Type, typename ... ConstructorArgs>
auto create(ConstructorArgs&& ... args){
    return std::make_unique<Type>(std::forward<ConstructorArgs>(args)...);
}

//Abstract Factory Base class
template<typename BaseType, typename ... ConstructorArgs>
class IFactory {
    public:
        virtual ~IFactory() = default;
        virtual std::unique_ptr<BaseType> create(ConstructorArgs&& ... args) const = 0;
};

//Abstract Factory class
template <typename BaseType, typename DerivedType, typename ... ConstructorArgs>
class CFactory : public IFactory<BaseType, ConstructorArgs...>
{
    public:
        using Base = IFactory<BaseType, ConstructorArgs...>;
        std::unique_ptr<BaseType> create(ConstructorArgs&& ... args) const override
        {
            return ::create<DerivedType>(std::forward<ConstructorArgs>(args)...);
        }
};

真正的 Factory classes 是如何定义的:

class TimerInterface {
    public:
        TimerInterface() = default;
        TimerInterface (const char* name);
        virtual void whoami() const = 0;
        /*...*/
};

class Timer: public TimerInterface {
    public:
        Timer() = default;
        Timer(const char* name) : TimerInterface (name) {}
        void whoami() const override { std::cerr << "I'm real!" << std::endl; }
        /*...*/
};


class TimerMock : public TimerInterface {
    public:
        TimerMock () = default;
        TimerMock (const char* name) : TimerInterface (name) {}
        void whoami() const override { std::cerr << "I fake it!" << std::endl; }
        /*...*/
};

using TimerFactory = CFactory<TimerInterface, Timer, const char*>;
using TimerMockFactory = CFactory<TimerInterface, TimerMock, const char*>;

using TimerFactoryInterface = TimerFactory::Base;

以及它们的用途:

class ClassUnderTest {
    public:
        std::unique_ptr<TimerInterface> timer {};
        std::unique_ptr<TimerInterface> timer2 {};
    
        ClassUnderTest(const TimerFactoryInterface& factory)
        : timer(factory.create("I got a name!"))
        //, timer2(factory.create())
        {}
};

class Production
{
    public:
        ClassUnderTest realUsage;
        
        Production() :
        realUsage(TimerFactory())
        {}
};

class Test
{
    public:
        ClassUnderTest tested;
        
        Test() :
        tested(TimerMockFactory())
        {}  
};

int main()
{
    Production p;
    p.realUsage.timer->whoami();
    
    Test t;
    t.tested.timer->whoami();
}

我最大的问题是需求 (4) ClassUnderTest::timer2 无法使用用于 ClassUnderTest::timer 的同一工厂创建,因为在定义 CFactory class.

时已经需要知道构造函数 signatur

有人知道吗?

P.S.: “这不可能”加上解释也是一个可以接受的答案,但不是我最喜欢的 ;)

不确定是否有帮助,但是当您在 ClassUnderTest 中声明两个 std::unique_ptr<TimerInterface> 时,TimerInterface 不需要是完整类型。这意味着这是合法的:

    // classundertest.hpp

    // A forward-declaration is enough
    class TimerInterface;
    class TimerFactoryInterface;

    class ClassUnderTest {
    public:
        std::unique_ptr<TimerInterface> timer {};
        std::unique_ptr<TimerInterface> timer2 {};
    
    // Both MUST be declared here AND defined in the .cpp for this trick to work
    ClassUnderTest(const TimerFactoryInterface& factory);
    ~ClassUnderTest();
    // classundertest.cpp

    #include "classundertest.hpp"

    // TimerInterface wasn't truly declared until now
    #include "TimerInterface.hpp"

    ClassUnderTest::ClassUnderTest(const TimerFactoryInterface& factory)
    : timer(factory.create("I got a name!"))
    , timer2(factory.create("???"))
    {}

    ClassUnderTest::~ClassUnderTest() 
    {}

这基本上就是 unique_ptr-based pimpl 的工作方式。

您可以使用 std::vector<std::any> 和一些索引序列来完成,但这不是最漂亮或最快速的解决方案。

您的工厂将是:

template<typename Base>
class IFactory
{
public:
  virtual std::unique_ptr<Base> Create(const std::vector<std::any>& args) const = 0;
};

您的混凝土工厂可以是:

template<typename Base, typename Derived, typename... Args>
class CFactory : IFactory<Base>
{
private:
  template<size_t... Indices>
  std::unique_ptr<Base> Make(const std::vector<std::any>& args, std::index_sequence<Indices...>) const
  {
    return std::make_unique<Derived>(std::any_cast<Args>(args[Indices])...);
  }

public:
  virtual std::unique_ptr<Base> Create(const std::vector<std::any>& args) const
  {
    return Make(args, std::make_index_sequence<sizeof...(Args)>());
  }
};

这里唯一的问题是你显然不能完美地转发任何论点,你总是会抄袭。 如果传入 std::any 的类型与模板不匹配,它也会抛出 std::bad_any_cast

这是我做的一个简单测试,它使用 MSVC 和 Clang 编译,-std=c++17 设置:

class A
{
public:
    A(int a, int b)
        : Val1(a), Val2(b)
    {}
    
    A(int a)
        : Val1(a), Val2(0)
    {}
    
    int Val1, Val2;
    
};

int main()
{
    CFactory<A, A, int> fac1;
    CFactory<A, A, int, int> fac2;
    
    auto a1 = fac1.Create({ std::any(10) });
    auto a2 = fac2.Create({ std::any(10), std::any(20) });
    
    std::cout << a1->Val1 << " " << a1->Val2 << "\n";
    std::cout << a2->Val1 << " " << a2->Val2 << "\n";
}

编辑:这将适用于重载的构造函数,以及任何类型,因为它的模板很神奇。

@SparkyPotato 在说我“必须手动列出可能的构造函数并为创建生成重载”时给了我想法。我只是不喜欢手动,所以我通过模板元编程来完成。谢谢提示!

#include <memory>
#include <iostream>
#include <tuple>


//"perfect factory method"
//usage: std::unique_ptr<Type> pt(create<Type>(Type-constructor-arguments));
template <typename Type, typename ... ConstructorArgs>
auto create(ConstructorArgs&& ... args){
    std::cerr << "calling " << __PRETTY_FUNCTION__ << std::endl;
    return std::make_unique<Type>(std::forward<ConstructorArgs>(args)...);
}

//Abstract Factory Variadic class. This is also generic case that ends recursion
template <typename BaseType, typename TupleListOfConstructorArgs>
class IFactory
{
    static_assert(std::is_same<TupleListOfConstructorArgs, std::tuple<>>::value, "");
    public:
        //this method shall never be instatiated, it just exists to satisfy the "using BaseFactory::create" in IFactory
        template <typename T> void create(){ static_assert(sizeof(BaseType) + sizeof(T) < 0, ""); }
        virtual ~IFactory() = default;
};

//Abstract Factory Variadic class specialization to perform inheritance recursion
template <typename BaseType, typename ... CurrentTupleArgs, typename ... TupleListOfConstructorArgs>
class IFactory<BaseType, std::tuple<std::tuple<CurrentTupleArgs...>, TupleListOfConstructorArgs...>> : public IFactory<BaseType, std::tuple<TupleListOfConstructorArgs...>>
{
    public:
        using BaseFactory = IFactory<BaseType, std::tuple<TupleListOfConstructorArgs...>>;
        using BaseFactory::create;
        virtual std::unique_ptr<BaseType> create(const CurrentTupleArgs&  ... args) const = 0;
};

//Concrete Factory Variadic class. This is also generic case that ends inheritance recursion
template <typename BaseType, typename DerivedType, typename TupleListOfConstructorArgs, typename FullTupleListOfConstructorArgs>
class CFactory : public IFactory<BaseType, FullTupleListOfConstructorArgs>
{
    static_assert(std::is_same<TupleListOfConstructorArgs, std::tuple<>>::value, "");
    public:
        using Base = IFactory<BaseType, FullTupleListOfConstructorArgs>;
};

//Concrete Factory Variadic class specialization to perform inheritance recursion
template <typename BaseType, typename DerivedType, typename ... CurrentTupleArgs, typename ... TupleListOfConstructorArgs, typename  FullTupleListOfConstructorArgs>
class CFactory<BaseType, DerivedType, std::tuple<std::tuple<CurrentTupleArgs...>, TupleListOfConstructorArgs...>, FullTupleListOfConstructorArgs> : public CFactory<BaseType, DerivedType, std::tuple<TupleListOfConstructorArgs...>, FullTupleListOfConstructorArgs>
{
    public:
        using BaseFactory = CFactory<BaseType, DerivedType, std::tuple<TupleListOfConstructorArgs...>, FullTupleListOfConstructorArgs>;
        using BaseFactory::create;

        std::unique_ptr<BaseType> create(const CurrentTupleArgs&  ... args) const override
        {
            std::cerr << "calling " << __PRETTY_FUNCTION__ << std::endl; 
            return ::create<DerivedType>(args...);
        }
};

template <typename BaseType, typename DerivedType, typename TupleListOfConstructorArgs>
using CFactoryFrontend = CFactory<BaseType, DerivedType, TupleListOfConstructorArgs, TupleListOfConstructorArgs>;

class TimerInterface {
    public:
        TimerInterface() = default;
        virtual ~TimerInterface() = default;
        TimerInterface (const char* name) {}
        TimerInterface(int& x, const char* name) { std::cerr << "calling " << __PRETTY_FUNCTION__ << std::endl;  }
        TimerInterface(const int& x, const char* name) { std::cerr << "calling " << __PRETTY_FUNCTION__ << std::endl;  }
        virtual void whoami() const = 0;
        /*...*/
};

class Timer: public TimerInterface {
    public:
        Timer() = default;
        Timer(const char* name) : TimerInterface (name) {}
        Timer(int& x, const char* name) : TimerInterface(x, name) {}
        Timer(const int& x, const char* name) : TimerInterface(x, name) {}
        void whoami() const override { std::cerr << "I'm real!" << std::endl; }
        /*...*/
};


class TimerMock : public TimerInterface {
    public:
        TimerMock () = default;
        TimerMock (const char* name) : TimerInterface (name) {}
        TimerMock (int& x, const char* name) : TimerInterface(x, name) {}
        TimerMock (const int& x, const char* name) : TimerInterface(x, name) {}
        void whoami() const override { std::cerr << "I fake it!" << std::endl; }
        /*...*/
};

//using TimerInterfaceConstructors = std::tuple<std::tuple<>, std::tuple<const char*>>;
using Constructors = std::tuple<std::tuple<>, std::tuple<const char*>, std::tuple<int&, const char*>, std::tuple<const int&, const char*>>;

using TestFactory = CFactoryFrontend<TimerInterface, Timer, Constructors>;

using TimerFactory = CFactoryFrontend<TimerInterface, Timer, Constructors>;
using TimerMockFactory = CFactoryFrontend<TimerInterface, TimerMock, Constructors>;

using TimerFactoryInterface = TimerFactory::Base;


class ClassUnderTest {
    public:
        std::unique_ptr<TimerInterface> timer {};
        std::unique_ptr<TimerInterface> timer2 {};
    
        ClassUnderTest(const TimerFactoryInterface& factory)
        : timer(factory.create("I got a name!"))
        , timer2(factory.create())
        {}
};

class Production
{
    public:
        ClassUnderTest realUsage;
        
        Production() :
        realUsage(TimerFactory())
        {}
};

class Test
{
    public:
        ClassUnderTest tested;
        
        Test() :
        tested(TimerMockFactory())
        {}  
};

int main()
{
    Production p;
    p.realUsage.timer->whoami();
    
    Test t;
    t.tested.timer->whoami();

    TestFactory tf;
    TimerFactoryInterface& tfi(tf);

    const char* x = "X";
    tfi.create();
    tfi.create(x);

    int y;
    const int cy = 17;
    tfi.create(y, x);
    tfi.create(cy, "name");

    ::create<Timer>(x);
}

它使用 GCC-6 和更新版本编译,并使用 -std=c++-14