Class 存储说明符(static/non 静态)作为模板参数以避免重复代码

Class storage specifier (static/non static) as template argument to avoid duplicate code

假设我有一个通用包装器 class 用于一个名为 worker 的对象,它具有许多方法:

template<typename T>
class Wrapper
{
private:
    T m_Worker;

public:
    void DoSomeWork1() { m_Worker.Work1(); }
    void DoSomeWork2() { m_Worker.Work2(); }
    // ...
};

现在我可以通过创建一个对象并调用一个成员来简单地使用我的 Wrapper class:

Wrapper<MyWorker> wrap;
wrap.DoSomeWork1();

但除了“本地”包装器 class,我还需要能够使用“静态”包装器 class (util class),以便能够在不先创建本地对象的情况下调用:

StaticWrapper<MyWorker>::DoSomeWork1();

StaticWrapper<T>Wrapper<T> 相同,但静态实现:

template<typename T>
class StaticWrapper
{
private:
    static T& Get()
    {
        static T worker;
        return worker;
    }

public:
    static void DoSomeWork1(); { Get().Work1(); }
    static void DoSomeWork2(); { Get().Work1(); }
    // ...
};

问题

上述解决方案使用两种不同的 classes 是可行的,但我必须将相同的代码编写两次,一次使用 static 关键字,一次不使用它。

是否有任何方法或设计模式可以避免两次编写相同的代码,即在一个名为 Wrapper 的 class 中有一个静态和非静态接口,然后决定使用哪个(是否静态)在编译期间创建/使用包装器时?本质上类似于将存储说明符设置为模板参数:

// Fantasy code
// To use a local Wrapper
Wrapper<Locally, MyWorker> localWrap;

// To use a static Wrapper
Wrapper<Statically, MyWorker>::DoSomeWork1();

另请注意: DoSomeWork1(), DoSomeWork2(), ... 方法中的代码比仅从 MyWorker 调用方法更多。所以直接使用静态(或不使用)MyWorker 不是一种选择。

让我们在稍微低一点的层面上分解一下:

template<typename T>
class NonStaticWrapper
{
private:
    T m_Worker;

public:
    T &Get() { return m_worker; }
};

然后就拿你已经写好的包装器:

template<typename T>
class StaticWrapper
{
private:
    static T& Get()
    {
        static T worker;
        return worker;
    }
};

现在您可以简单地实现包装器,如下所示:


template<typename T>
class Wrapper : private T
{
public:
    void DoSomeWork1() { this->Get().Work1(); }
    void DoSomeWork2() { this->Get().Work2(); }
    // ...
};

然后实例化 Wrapper<NonStaticWrapper<T>>Wrapper<StaticWrapper<T>>(1).

对于 Wrapper,您可以选择也可以不选择使用 public 或私有继承。构造 (2) 可能需要一些额外的工作。但这些只是次要的细节。

(1) this->Get()T::Get() 在这里通常是等效的,这是一个偏好问题。

(2) 你可能需要使用类似

Wrapper<StaticWrapper<T>>{}.DoSomeWork1();

调用静态版本。

您可以保留静态 API 而不是 T 本身,而是 Wrapper。

带有“static API”的结果 class 可能如下所示:

template <typename T>
class StaticWrapper {
private:
    static Wrapper<T>& Get() {
        static Wrapper<T> worker;
        return worker;
    }

public:
    static void DoSomeWork1() { Get().DoSomeWork1(); }
    static void DoSomeWork2() { Get().DoSomeWork2(); }
    // ...
};

问题更新: 另一种更好的方法(没有单独的 StaticWrapper class)

template <typename T>
class Wrapper {
private:
    T m_Worker;

public:
    // Standard API to be implemented
    void DoSomeWork1() { m_Worker.Work1(); }
    void DoSomeWork2() { m_Worker.Work2(); }

    // Static API just wraps Standard API
    static void DoSomeWork1_Static() {
        static Wrapper<T> x;
        x.DoSomeWork1();
    }
    static void DoSomeWork2_Static() {
        static Wrapper<T> x;
        x.DoSomeWork2();
    }
};

void call_examples() {
    Wrapper<w1> w1;
    w1.DoSomeWork1();
    Wrapper<w1>::DoSomeWork1_Static();
    Wrapper<w2>::DoSomeWork2_Static();
}

(您可以在下面找到原始答案和方法)

的优点最多,可以扩展到你想要的。使用 Wrapper 本身作为静态成员并委托给它。要获得您想要的语法,您只需使用“虚拟策略”参数和一些模板专门化来构建代码。

struct MemberData;
struct StaticData;

template<typename T, typename StoragePolicy = MemberData>
class Wrapper;

template<typename T>
class Wrapper<T, MemberData>
{
private:
    T m_Worker;

public:
    void DoSomeWork1() { m_Worker.Work1(); }
    void DoSomeWork2() { m_Worker.Work2(); }
    // ...
};

template<typename T>
class Wrapper<T, StaticData>
{
private:
    static auto& get() {
        static Wrapper<T, MemberData> obj;
        return obj;
    }

public:
    static void DoSomeWork1() { get().DoSomeWork1(); }
    static void DoSomeWork2() { get().DoSomeWork2(); }
    // ...
};

// ... 

int main(int, char **)
{
    //Wrapper<MyWorker>::DoSomeWork1(); //error
    Wrapper<MyWorker> wrap;
    wrap.DoSomeWork1();

    Wrapper<MyWorker, StaticData>::DoSomeWork1();
}

总而言之,这要简单得多,并且很好地避免了重复实现。仍然需要一些样板来获得等效的成员集。


您可以通过一些重组和基于策略的设计来获得您想要的东西。但是样板的数量可能不值得。

我们可以将变量(及其)关联成员函数的存储抽象为策略(在本例中,表示为模板模板参数,但它可以是类型参数)。然后 Wrapper 公开策略的成员,并提供该实现(作为接受数据进行操作的静态成员)。

template<class Worker, template<class> class Storage>
class Wrapper;

template<class Worker>
class MemberData {
  Worker m_Worker;
protected:
  void DoSomeWork1() { Wrapper<Worker, ::MemberData>::DoSomeWork1Impl(m_Worker); }
  void DoSomeWork2() { Wrapper<Worker, ::MemberData>::DoSomeWork2Impl(m_Worker); }
};

template<class Worker>
class StaticData {
  static auto& get() {
    static Worker worker;
    return worker;
  }
protected:
  static void DoSomeWork1() { Wrapper<Worker, ::StaticData>::DoSomeWork1Impl(get()); }
  static void DoSomeWork2() { Wrapper<Worker, ::StaticData>::DoSomeWork2Impl(get()); }
};

template<class Worker, template<class> class Storage = MemberData>
class Wrapper : private Storage<Worker> {
    friend class Storage<Worker>;
    static void DoSomeWork1Impl(Worker&) { /*...*/ }
    static void DoSomeWork2Impl(Worker&) { /*...*/ }
public:
    using Storage<Worker>::DoSomeWork1;
    using Storage<Worker>::DoSomeWork2;
};

int main(int, char **)
{
    //Wrapper<int>::DoSomeWork1(); //error
    Wrapper<int> wrap;
    wrap.DoSomeWork1();
    
    Wrapper<int, StaticData>::DoSomeWork1();
}

正如您可以从一个简单示例的此实现的复杂性中判断的那样,“条件静态”很难实现。如果 DoSomeWork1Impl 依赖于 Wrapper 中的数据,那么它将变得更加复杂。